diff options
author | Svetoslav Ganov <svetoslavganov@google.com> | 2011-01-26 14:39:43 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-01-26 14:39:43 -0800 |
commit | 1e31eaae2b57a140a60c3469077f5aeeeb2db9c9 (patch) | |
tree | 72153bb650424c7d52ddfe2611982e50e115324a /WebKit | |
parent | 4307fcfd3fd2f4670e174c266bbbcf50849a19ca (diff) | |
parent | 1b7a61602d4e5a47094a3eaf8d1d9d12fb78402d (diff) | |
download | external_webkit-1e31eaae2b57a140a60c3469077f5aeeeb2db9c9.zip external_webkit-1e31eaae2b57a140a60c3469077f5aeeeb2db9c9.tar.gz external_webkit-1e31eaae2b57a140a60c3469077f5aeeeb2db9c9.tar.bz2 |
Merge "Final polish of the WebView accessibility support for JavaAcript disabled case" into honeycomb
Diffstat (limited to 'WebKit')
-rw-r--r-- | WebKit/android/jni/WebViewCore.cpp | 728 | ||||
-rw-r--r-- | WebKit/android/jni/WebViewCore.h | 14 | ||||
-rw-r--r-- | WebKit/android/nav/WebView.cpp | 28 |
3 files changed, 512 insertions, 258 deletions
diff --git a/WebKit/android/jni/WebViewCore.cpp b/WebKit/android/jni/WebViewCore.cpp index fe1d0db..0163376 100644 --- a/WebKit/android/jni/WebViewCore.cpp +++ b/WebKit/android/jni/WebViewCore.cpp @@ -36,6 +36,8 @@ #include "ChromeClientAndroid.h" #include "ChromiumIncludes.h" #include "Color.h" +#include "CSSPropertyNames.h" +#include "CSSValueKeywords.h" #include "DatabaseTracker.h" #include "Document.h" #include "DOMWindow.h" @@ -112,6 +114,7 @@ #include "WindowsKeyboardCodes.h" #include "android_graphics.h" #include "autofill/WebAutoFill.h" +#include "htmlediting.h" #include "markup.h" #include <JNIHelp.h> @@ -294,6 +297,7 @@ struct WebViewCore::JavaGlue { jmethodID m_setScrollbarModes; jmethodID m_setInstallableWebApp; jmethodID m_setWebTextViewAutoFillable; + jmethodID m_selectAt; AutoJObject object(JNIEnv* env) { return getRealObject(env, m_obj); } @@ -389,6 +393,7 @@ WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* m m_javaGlue->m_setScrollbarModes = GetJMethod(env, clazz, "setScrollbarModes", "(II)V"); m_javaGlue->m_setInstallableWebApp = GetJMethod(env, clazz, "setInstallableWebApp", "()V"); m_javaGlue->m_setWebTextViewAutoFillable = GetJMethod(env, clazz, "setWebTextViewAutoFillable", "(ILjava/lang/String;)V"); + m_javaGlue->m_selectAt = GetJMethod(env, clazz, "selectAt", "(II)V"); env->DeleteLocalRef(clazz); env->SetIntField(javaWebViewCore, gWebViewCoreFields.m_nativeClass, (jint)this); @@ -2078,39 +2083,6 @@ String WebViewCore::modifySelection(const int direction, const int axis) } } -String WebViewCore::moveSelection(WebCore::Frame* frame, WebCore::Node* node) -{ - if (!frame || !node) - return String(); - - if (!CacheBuilder::validNode(m_mainFrame, frame, node)) - return String(); - - PassRefPtr<Range> rangeRef = 0; - ExceptionCode ec = 0; - DOMSelection* selection = frame->domWindow()->getSelection(); - if (selection->rangeCount() > 0) { - rangeRef = selection->getRangeAt(0, ec); - if (ec) - return String(); - selection->removeAllRanges(); - } else { - rangeRef = frame->document()->createRange(); - } - - rangeRef->selectNode(node, ec); - if (ec) - return String(); - - selection->addRange(rangeRef.get()); - - scrollNodeIntoView(frame, node); - - String markup = formatMarkup(selection).stripWhiteSpace(); - LOGV("Selection markup: %s", markup.utf8().data()); - return markup; -} - void WebViewCore::scrollNodeIntoView(Frame* frame, Node* node) { if (!frame || !node) @@ -2135,178 +2107,410 @@ void WebViewCore::scrollNodeIntoView(Frame* frame, Node* node) String WebViewCore::modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int axis) { - String directionString; - if (direction == DIRECTION_FORWARD) - directionString = "forward"; - else if (direction == DIRECTION_BACKWARD) - directionString = "backward"; - else { - LOGE("Invalid direction: %d", direction); - return String(); - } - String axisString; - if (axis == AXIS_CHARACTER) - axisString = "character"; - else if (axis == AXIS_WORD) - axisString = "word"; - else // axis == AXIS_SENTENCE - axisString = "sentence"; - // TODO: Add support of IFrames. HTMLElement* body = m_mainFrame->document()->body(); - Node* focusNode = 0; - if (m_currentNodeDomNavigationAxis - && CacheBuilder::validNode(m_mainFrame, m_mainFrame, m_currentNodeDomNavigationAxis)) { - focusNode = m_currentNodeDomNavigationAxis; - m_currentNodeDomNavigationAxis = 0; - do { - focusNode = (direction == DIRECTION_FORWARD) ? - focusNode->traverseNextNode(body) : - focusNode->traversePreviousNode(body); - } while (focusNode && focusNode->isTextNode()); - } else - focusNode = (selection->focusNode()) ? selection->focusNode() : currentFocus(); - - Text* currentNode = 0; - if (!focusNode) { - // we have no selection so start from the body or its recursively last child - focusNode = (direction == DIRECTION_FORWARD) ? body : body->lastDescendant(); - if (focusNode->isTextNode()) - currentNode = static_cast<Text*>(focusNode); - else - currentNode = traverseNonEmptyNonWhitespaceTextNode(focusNode, body, direction); - if (!setSelection(selection, currentNode, direction)) + ExceptionCode ec = 0; + + // initialize the selection if necessary + if (selection->rangeCount() == 0) { + if (m_currentNodeDomNavigationAxis + && CacheBuilder::validNode(m_mainFrame, + m_mainFrame, m_currentNodeDomNavigationAxis)) { + PassRefPtr<Range> rangeRef = + selection->frame()->document()->createRange(); + rangeRef->selectNode(m_currentNodeDomNavigationAxis, ec); + m_currentNodeDomNavigationAxis = 0; + if (ec) + return String(); + selection->addRange(rangeRef.get()); + } else if (currentFocus()) { + selection->setPosition(currentFocus(), 0, ec); + } else if (m_cursorNode + && CacheBuilder::validNode(m_mainFrame, + m_mainFrame, m_cursorNode)) { + PassRefPtr<Range> rangeRef = + selection->frame()->document()->createRange(); + rangeRef->selectNode(reinterpret_cast<Node*>(m_cursorNode), ec); + if (ec) + return String(); + selection->addRange(rangeRef.get()); + } else if (direction == DIRECTION_FORWARD) { + selection->setPosition(body->firstDescendant(), 0, ec); + } else { + selection->setPosition(body->lastDescendant(), 0, ec); + } + if (ec) return String(); - } else if (focusNode->isElementNode()) { - // find a non-empty text node in the current direction - currentNode = traverseNonEmptyNonWhitespaceTextNode(focusNode, body, direction); - if (!setSelection(selection, currentNode, direction)) + } + // collapse the selection + if (direction == DIRECTION_FORWARD) + selection->collapseToEnd(ec); + else + selection->collapseToStart(ec); + if (ec) + return String(); + + Node* oldAnchorNode = selection->anchorNode(); + if (!oldAnchorNode) + return String(); + + // Make sure the anchor node is a text node since we are generating + // the markup of the selection which includes the anchor, the focus, + // and any crossed nodes. Forcing the condition that the selection + // starts and ends on text nodes guarantees symmetric selection markup. + Node* anchorNode = selection->anchorNode(); + if (anchorNode->isElementNode()) { + int anchorOffset = rangeCompliantChildOffset(anchorNode, + selection->anchorOffset()); + anchorNode = selection->anchorNode()->childNodes()->item(anchorOffset); + Node* nextAnchorNode = traverseVisibleNonEmptyNonWhitespaceTextNode( + anchorNode, body, direction); + if (!nextAnchorNode) return String(); - } else { - currentNode = static_cast<Text*>(focusNode); if (direction == DIRECTION_FORWARD) { - // if end of non-whitespace text go to the next non-empty text node - int higherIndex = (selection->focusOffset() > selection->anchorOffset()) ? - selection->focusOffset() : selection->anchorOffset(); - String suffix = currentNode->data().substring(higherIndex, currentNode->length()); - if (suffix.isEmpty() || suffix.stripWhiteSpace().isEmpty()) { - currentNode = traverseNonEmptyNonWhitespaceTextNode(currentNode, body, direction); - if (!setSelection(selection, currentNode, direction)) + Node* skippedControl = getFirstIntermediaryInputOrButton(anchorNode, + nextAnchorNode); + if (skippedControl) { + IntRect bounds = static_cast<Element*>( + skippedControl)->boundsInWindowSpace(); + selectAt(bounds.center().x(), bounds.center().y()); + selection->setBaseAndExtent(skippedControl, + caretMinOffset(skippedControl), skippedControl, + caretMaxOffset(skippedControl), ec); + if (ec) return String(); + return formatMarkup(selection).stripWhiteSpace(); } else { - ExceptionCode ec = 0; - selection->collapseToEnd(ec); + selection->setPosition(nextAnchorNode, 0, ec); if (ec) - LOGE("Error while collapsing selection. Error code: %d", ec); + return String(); } } else { - // if beginning of non-whitespace text go to the previous non-empty text node - int lowerIndex = (selection->focusOffset() > selection->anchorOffset()) ? - selection->anchorOffset() : selection->focusOffset(); - String prefix = currentNode->data().substring(0, lowerIndex); - if (prefix.isEmpty() || prefix.stripWhiteSpace().isEmpty()) { - currentNode = traverseNonEmptyNonWhitespaceTextNode(currentNode, body, direction); - if (!setSelection(selection, currentNode, direction)) + Node* skippedControl = getFirstIntermediaryInputOrButton( + nextAnchorNode, anchorNode); + if (skippedControl) { + IntRect bounds = static_cast<Element*>( + skippedControl)->boundsInWindowSpace(); + selectAt(bounds.center().x(), bounds.center().y()); + selection->setBaseAndExtent(skippedControl, + caretMaxOffset(skippedControl), skippedControl, + caretMinOffset(skippedControl), ec); + if (ec) return String(); + return formatMarkup(selection).stripWhiteSpace(); } else { - ExceptionCode ec = 0; - selection->collapseToStart(ec); + selection->setPosition(nextAnchorNode, + caretMaxOffset(nextAnchorNode), ec); if (ec) - LOGE("Error while collapsing selection. Error code: %d", ec); + return String(); } } } - // extend the selection - loop as an insurance it does not get stuck - currentNode = static_cast<Text*>(selection->focusNode()); - int focusOffset = selection->focusOffset(); - while (true) { - selection->modify("extend", directionString, axisString); - if (selection->focusNode() != currentNode || selection->focusOffset() != focusOffset) - break; - currentNode = traverseNonEmptyNonWhitespaceTextNode(currentNode, body, direction); - focusOffset = (direction == DIRECTION_FORWARD) ? 0 : currentNode->data().length(); - // setSelection returns false if currentNode is 0 => the loop always terminates - if (!setSelection(selection, currentNode, direction)) - return String(); + // If the selection is at the end of a non white space text move + // it to the next visible text node with non white space content. + // This is a workaround for the selection getting stuck. + anchorNode = selection->anchorNode(); + if (!anchorNode) + return String(); + if (anchorNode->isTextNode()) { + if (direction == DIRECTION_FORWARD) { + String suffix = anchorNode->textContent().substring( + selection->anchorOffset(), caretMaxOffset(anchorNode)); + if (suffix.stripWhiteSpace().isEmpty()) { + Node* nextAnchorNode = + traverseVisibleNonEmptyNonWhitespaceTextNode(anchorNode, + body, direction); + if (!nextAnchorNode) + return String(); + Node* skippedControl = getFirstIntermediaryInputOrButton( + anchorNode, nextAnchorNode); + if (skippedControl) { + IntRect bounds = static_cast<Element*>( + skippedControl)->boundsInWindowSpace(); + selectAt(bounds.center().x(), bounds.center().y()); + selection->setBaseAndExtent(skippedControl, + caretMinOffset(skippedControl), skippedControl, + caretMaxOffset(skippedControl), ec); + if (ec) + return String(); + return formatMarkup(selection).stripWhiteSpace(); + } else { + selection->setPosition(nextAnchorNode, 0, ec); + if (ec) + return String(); + } + } + } else { + String prefix = anchorNode->textContent().substring(0, + selection->anchorOffset()); + if (prefix.stripWhiteSpace().isEmpty()) { + Node* nextAnchorNode = + traverseVisibleNonEmptyNonWhitespaceTextNode(anchorNode, + body, direction); + if (!nextAnchorNode) + return String(); + Node* skippedControl = getFirstIntermediaryInputOrButton( + nextAnchorNode, anchorNode); + if (skippedControl) { + IntRect bounds = static_cast<Element*>( + skippedControl)->boundsInWindowSpace(); + selectAt(bounds.center().x(), bounds.center().y()); + selection->setBaseAndExtent(skippedControl, + caretMaxOffset(skippedControl), skippedControl, + caretMinOffset(skippedControl), ec); + if (ec) + return String(); + return formatMarkup(selection).stripWhiteSpace(); + } else { + selection->setPosition(nextAnchorNode, + caretMaxOffset(nextAnchorNode), ec); + if (ec) + return String(); + } + } + } } - if (direction == DIRECTION_FORWARD) { - // enforce the anchor node is a text node - if (selection->anchorNode()->isElementNode()) { - if (!setSelection(selection, selection->focusNode(), selection->focusNode(), 0, - selection->focusOffset())) - return String(); - } - // enforce the focus node is a text node - if (selection->focusNode()->isElementNode()) { - int endOffset = static_cast<Text*>(selection->anchorNode())->length(); // cast is safe - if (!setSelection(selection, selection->anchorNode(), selection->anchorNode(), - selection->anchorOffset(), endOffset)) + // extend the selection + String directionStr; + if (direction == DIRECTION_FORWARD) + directionStr = "forward"; + else + directionStr = "backward"; + + String axisStr; + if (axis == AXIS_CHARACTER) + axisStr = "character"; + else if (axis == AXIS_WORD) + axisStr = "word"; + else + axisStr = "sentence"; + + selection->modify("extend", directionStr, axisStr); + + // Make sure the focus node is a text node in order to have the + // selection generate symmetric markup because the latter + // includes all nodes crossed by the selection. + Node* focusNode = selection->focusNode(); + if (focusNode->isElementNode()) { + int focusOffset = rangeCompliantChildOffset(focusNode, + selection->focusOffset()); + if (direction == DIRECTION_FORWARD) { + focusNode = + focusNode->childNodes()->item(focusOffset)->lastDescendant(); + if (!isVisibleNonEmptyNonWhitespaceTextNode(focusNode)) { + focusNode = traverseVisibleNonEmptyNonWhitespaceTextNode( + focusNode, body, DIRECTION_BACKWARD); + if (!focusNode) + return String(); + } + selection->extend(focusNode, caretMaxOffset(focusNode), ec); + if (ec) return String(); - } - } else { - // enforce the focus node is a text node - if (selection->focusNode()->isElementNode()) { - if (!setSelection(selection, selection->anchorNode(), selection->anchorNode(), 0, - selection->anchorOffset())) + } else { + focusNode = + focusNode->childNodes()->item(focusOffset)->firstDescendant(); + if (!isVisibleNonEmptyNonWhitespaceTextNode(focusNode)) { + focusNode = traverseVisibleNonEmptyNonWhitespaceTextNode( + focusNode, body, DIRECTION_FORWARD); + if (!focusNode) + return String(); + } + selection->extend(focusNode, 0, ec); + if (ec) return String(); } - // enforce the anchor node is a text node - if (selection->anchorNode()->isElementNode()) { - int endOffset = static_cast<Text*>(selection->focusNode())->length(); // cast is safe - if (!setSelection(selection, selection->focusNode(), selection->focusNode(), - selection->focusOffset(), endOffset)) - return String(); + } + + // Enforce that the selection does not cross anchor boundaries. This is + // a workaround for the asymmetric behavior of WebKit while crossing + // anchors. + // NOTE: The code is asymmetric since the logic is based off the common + // ancestor in both directions - backward and forward. + // TODO: Factor out common code repeated below. + anchorNode = selection->anchorNode(); + focusNode = selection->focusNode(); + if (anchorNode != focusNode + && anchorNode->isTextNode() + && focusNode->isTextNode()) { + Node* commonAncestor = Range::commonAncestorContainer(anchorNode, + focusNode); + Node* currentNode = 0; + bool selectionAdjusted = false; + if (direction == DIRECTION_FORWARD) { + // catch if the anchor is in a link but the focus is not + if (!commonAncestor->hasTagName(WebCore::HTMLNames::aTag)) { + currentNode = anchorNode; + while (currentNode != commonAncestor) { + if (isVisible(currentNode) && isInputControl(currentNode)) { + focusNode = currentNode->lastDescendant(); + if (!isVisibleNonEmptyNonWhitespaceTextNode(focusNode)) { + focusNode = + traverseVisibleNonEmptyNonWhitespaceTextNode( + focusNode, commonAncestor, + DIRECTION_BACKWARD); + if (!focusNode) + return String(); + } + selection->extend(focusNode, caretMaxOffset(focusNode), + ec); + if (ec) + return String(); + selectionAdjusted = true; + break; + } + currentNode = currentNode->parentNode(); + } + // catch if there is a link between the anchor and focus + if (!selectionAdjusted) { + currentNode = anchorNode; + while (currentNode != focusNode) { + if (isVisible(currentNode) + && isInputControl(currentNode)) { + focusNode = currentNode; + if (!isVisibleNonEmptyNonWhitespaceTextNode(focusNode)) { + focusNode = + traverseVisibleNonEmptyNonWhitespaceTextNode( + focusNode, commonAncestor, + DIRECTION_BACKWARD); + if (!focusNode) + return String(); + } + selection->extend(focusNode, + caretMaxOffset(focusNode), ec); + if (ec) + return String(); + break; + } + currentNode = currentNode->traverseNextNode(); + } + } + } + } else { + // catch if the anchor is in a link but the focus is not + // NOTE: There is not such case in forward direction because + // it is implicitly covered the second case. Also the + // base position used for computing the the common + // ancestor which is asymmteric. + if (!commonAncestor->hasTagName(WebCore::HTMLNames::aTag)) { + currentNode = anchorNode; + while (currentNode != commonAncestor) { + if (isVisible(currentNode) && isInputControl(currentNode)) { + focusNode = currentNode->firstDescendant(); + if (!isVisibleNonEmptyNonWhitespaceTextNode(focusNode)) { + focusNode = + traverseVisibleNonEmptyNonWhitespaceTextNode( + focusNode, commonAncestor, + DIRECTION_FORWARD); + if (!focusNode) + return String(); + } + selection->extend(focusNode, 0, ec); + if (ec) + return String(); + selectionAdjusted = true; + break; + } + currentNode = currentNode->parentNode(); + } + // catch if there is a link between the anchor and focus + if (!selectionAdjusted) { + currentNode = anchorNode; + while (currentNode != focusNode) { + if (isVisible(currentNode) + && isInputControl(currentNode)) { + focusNode = currentNode->traverseNextSibling(); + if (!isVisibleNonEmptyNonWhitespaceTextNode(focusNode)) { + focusNode = + traverseVisibleNonEmptyNonWhitespaceTextNode( + focusNode, commonAncestor, + DIRECTION_FORWARD); + if (!focusNode) + return String(); + } + selection->extend(focusNode, 0, ec); + if (ec) + return String(); + selectionAdjusted = true; + break; + } + currentNode = currentNode->traversePreviousNode(); + } + } + // catch if the focus is in a link but the anchor is not + if (!selectionAdjusted) { + currentNode = focusNode; + while (currentNode != commonAncestor) { + if (isVisible(currentNode) + && isInputControl(currentNode)) { + focusNode = currentNode->traverseNextSibling(); + if (!isVisibleNonEmptyNonWhitespaceTextNode(focusNode)) { + focusNode = + traverseVisibleNonEmptyNonWhitespaceTextNode( + focusNode, commonAncestor, + DIRECTION_FORWARD); + if (!focusNode) + return String(); + } + selection->extend(focusNode, 0, ec); + if (ec) + return String(); + break; + } + currentNode = currentNode->parentNode(); + } + } + } } } + // make sure the selection is visible if (direction == DIRECTION_FORWARD) - scrollNodeIntoView(m_mainFrame, selection->focusNode()); + scrollNodeIntoView(m_mainFrame, selection->focusNode()); else - scrollNodeIntoView(m_mainFrame, selection->anchorNode()); + scrollNodeIntoView(m_mainFrame, selection->anchorNode()); - tryFocusInlineSelectionElement(selection); + // format markup for the visible content + PassRefPtr<Range> range = selection->getRangeAt(0, ec); + if (ec) + return String(); + IntRect bounds = range->boundingBox(); + selectAt(bounds.center().x(), bounds.center().y()); String markup = formatMarkup(selection).stripWhiteSpace(); LOGV("Selection markup: %s", markup.utf8().data()); + return markup; } -bool WebViewCore::setSelection(DOMSelection* selection, Text* textNode, int direction) +bool WebViewCore::isInputControl(Node* node) { - if (!textNode) - return false; - int offset = (direction == DIRECTION_FORWARD) ? 0 : textNode->length(); - if (!setSelection(selection, textNode, textNode, offset, offset)) - return false; - return true; + return (node->hasTagName(WebCore::HTMLNames::aTag) + || node->hasTagName(WebCore::HTMLNames::inputTag) + || node->hasTagName(WebCore::HTMLNames::buttonTag)); } -bool WebViewCore::setSelection(DOMSelection* selection, Node* startNode, Node* endNode, int startOffset, int endOffset) +int WebViewCore::rangeCompliantChildOffset(Node* parent, int offset) { - if (!selection || (!startNode && !endNode)) - return false; - ExceptionCode ec = 0; - PassRefPtr<Range> rangeRef = selection->getRangeAt(0, ec); - if (ec) { - ec = 0; - rangeRef = m_mainFrame->document()->createRange(); - } - if (startNode) - rangeRef->setStart(PassRefPtr<Node>(startNode), startOffset, ec); - if (ec) - return false; - if (endNode) - rangeRef->setEnd(PassRefPtr<Node>(endNode), endOffset, ec); - if (ec) - return false; - selection->removeAllRanges(); - selection->addRange(rangeRef.get()); - return true; + if (offset < 0) + return 0; + int lastChildIndex = parent->childNodes()->length() - 1; + if (offset > lastChildIndex) + return lastChildIndex; + return offset; } -Text* WebViewCore::traverseNonEmptyNonWhitespaceTextNode(Node* fromNode, Node* toNode, int direction) +bool WebViewCore::isVisibleNonEmptyNonWhitespaceTextNode(Node* node) +{ + if (!node || !node->isTextNode()) + return false; + Text* textNode = static_cast<Text*>(node); + return (isVisible(textNode) && textNode->length() > 0 + && !textNode->containsOnlyWhitespace()); +} + +Text* WebViewCore::traverseVisibleNonEmptyNonWhitespaceTextNode(Node* fromNode, Node* toNode, int direction) { Node* currentNode = fromNode; do { @@ -2314,18 +2518,10 @@ Text* WebViewCore::traverseNonEmptyNonWhitespaceTextNode(Node* fromNode, Node* t currentNode = currentNode->traverseNextNode(toNode); else currentNode = currentNode->traversePreviousNode(toNode); - } while (currentNode && (!currentNode->isTextNode() - || isEmptyOrOnlyWhitespaceTextNode(currentNode))); + } while (currentNode && !isVisibleNonEmptyNonWhitespaceTextNode(currentNode)); return static_cast<Text*>(currentNode); } -bool WebViewCore::isEmptyOrOnlyWhitespaceTextNode(Node* node) -{ - return (node->isTextNode() - && (static_cast<Text*>(node)->length() == 0 - || static_cast<Text*>(node)->containsOnlyWhitespace())); -} - String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int axis) { // TODO: Add support of IFrames. @@ -2334,12 +2530,14 @@ String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, in m_currentNodeDomNavigationAxis = selection->focusNode(); selection->empty(); if (m_currentNodeDomNavigationAxis->isTextNode()) - m_currentNodeDomNavigationAxis = m_currentNodeDomNavigationAxis->parentNode(); + m_currentNodeDomNavigationAxis = + m_currentNodeDomNavigationAxis->parentNode(); } if (!m_currentNodeDomNavigationAxis) m_currentNodeDomNavigationAxis = currentFocus(); if (!m_currentNodeDomNavigationAxis - || !CacheBuilder::validNode(m_mainFrame, m_mainFrame, m_currentNodeDomNavigationAxis)) + || !CacheBuilder::validNode(m_mainFrame, m_mainFrame, + m_currentNodeDomNavigationAxis)) m_currentNodeDomNavigationAxis = body; Node* currentNode = m_currentNodeDomNavigationAxis; if (axis == AXIS_HEADING) { @@ -2350,19 +2548,21 @@ String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, in currentNode = currentNode->traverseNextNode(body); else currentNode = currentNode->traversePreviousNode(body); - } while (currentNode && (currentNode->isTextNode() || !isVisible(currentNode) - || !isHeading(currentNode))); + } while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode) || !isHeading(currentNode))); } else if (axis == AXIS_PARENT_FIRST_CHILD) { if (direction == DIRECTION_FORWARD) { currentNode = currentNode->firstChild(); - while (currentNode && (currentNode->isTextNode() || !isVisible(currentNode))) + while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode))) currentNode = currentNode->nextSibling(); } else { do { if (currentNode == body) return String(); currentNode = currentNode->parentNode(); - } while (currentNode && (currentNode->isTextNode() || !isVisible(currentNode))); + } while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode))); } } else if (axis == AXIS_SIBLING) { do { @@ -2373,7 +2573,8 @@ String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, in return String(); currentNode = currentNode->previousSibling(); } - } while (currentNode && (currentNode->isTextNode() || !isVisible(currentNode))); + } while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode))); } else if (axis == AXIS_DOCUMENT) { currentNode = body; if (direction == DIRECTION_FORWARD) @@ -2385,8 +2586,6 @@ String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, in if (currentNode) { m_currentNodeDomNavigationAxis = currentNode; scrollNodeIntoView(m_mainFrame, currentNode); - focusIfFocusableAndNotTextInput(selection, currentNode); - // TODO (svetoslavganov): Draw the selected text in the WebView - a-la-Android String selectionString = createMarkup(currentNode); LOGV("Selection markup: %s", selectionString.utf8().data()); return selectionString; @@ -2407,7 +2606,8 @@ bool WebViewCore::isHeading(Node* node) if (node->isElementNode()) { Element* element = static_cast<Element*>(node); - String roleAttribute = element->getAttribute(WebCore::HTMLNames::roleAttr).string(); + String roleAttribute = + element->getAttribute(WebCore::HTMLNames::roleAttr).string(); if (equalIgnoringCase(roleAttribute, "heading")) return true; } @@ -2417,60 +2617,129 @@ bool WebViewCore::isHeading(Node* node) bool WebViewCore::isVisible(Node* node) { - if (!node->isStyledElement()) - return false; - RenderStyle* style = node->computedStyle(); - return (style->display() != NONE && style->visibility() != HIDDEN); + // TODO: Use DOMWindow#getComputedStyle instead. + Node* body = m_mainFrame->document()->body(); + Node* currentNode = node; + while (currentNode && currentNode != body) { + RenderStyle* style = currentNode->computedStyle(); + if (style->display() == NONE || style->visibility() == HIDDEN) { + return false; + } + currentNode = currentNode->parentNode(); + } + return true; } String WebViewCore::formatMarkup(DOMSelection* selection) { ExceptionCode ec = 0; - PassRefPtr<Range> rangeRef = selection->getRangeAt(0, ec); - if (ec) { - LOGE("Error accessing the first selection range. Error code: %d", ec); - return String(); - } - // TODO: This breaks in certain cases - WebKit bug. Figure out and work around it - String markup = createMarkup(rangeRef.get()); - int fromIdx = markup.find("<span class=\"Apple-style-span\""); - while (fromIdx > -1) { - unsigned toIdx = markup.find(">"); - markup = markup.replace(fromIdx, toIdx - fromIdx + 1, ""); - markup = markup.replace("</span>", ""); - fromIdx = markup.find("<span class=\"Apple-style-span\""); + String markup = String(); + + PassRefPtr<Range> wholeRange = selection->getRangeAt(0, ec); + if (ec) + return markup; + + if (!wholeRange->startContainer() || !wholeRange->startContainer()) + return markup; + + // Since formatted markup contains invisible nodes it + // is created from the concatenation of the visible fragments. + Node* firstNode = wholeRange->firstNode(); + Node* pastLastNode = wholeRange->pastLastNode(); + Node* currentNode = firstNode; + PassRefPtr<Range> currentRange; + while (currentNode != pastLastNode) { + Node* nextNode = currentNode->traverseNextNode(); + if (!isVisible(currentNode)) { + if (currentRange) { + markup = markup + stripAppleSpanFromMarkup( + currentRange->toHTML()).utf8().data(); + currentRange = 0; + } + } else { + if (!currentRange) { + currentRange = selection->frame()->document()->createRange(); + if (ec) + return markup; + if (currentNode == firstNode) { + currentRange->setStart(wholeRange->startContainer(), + wholeRange->startOffset(), ec); + if (ec) + return markup; + } else { + currentRange->setStart(currentNode->parentNode(), + currentNode->nodeIndex(), ec); + if (ec) + return markup; + } + } + if (nextNode == pastLastNode) { + currentRange->setEnd(wholeRange->endContainer(), + wholeRange->endOffset(), ec); + if (ec) + return markup; + markup = markup + stripAppleSpanFromMarkup( + currentRange->toHTML()).utf8().data(); + } else { + if (currentNode->offsetInCharacters()) + currentRange->setEnd(currentNode, + currentNode->maxCharacterOffset(), ec); + else + currentRange->setEnd(currentNode->parentNode(), + currentNode->nodeIndex() + 1, ec); + if (ec) + return markup; + } + } + currentNode = nextNode; } return markup; } -void WebViewCore::tryFocusInlineSelectionElement(DOMSelection* selection) +String WebViewCore::stripAppleSpanFromMarkup(String markup) { - Node* currentNode = selection->anchorNode(); - Node* endNode = selection->focusNode(); - while (currentNode) { - if (focusIfFocusableAndNotTextInput(selection, currentNode)) - return; - currentNode = currentNode->traverseNextNode(endNode); - } + int fromIdx = markup.find("<span class=\"Apple-style-span\""); + while (fromIdx > -1) { + int toIdx = markup.find(">"); + markup = markup.replace(fromIdx, toIdx - fromIdx + 1, ""); + markup = markup.replace("</span>", ""); + fromIdx = markup.find("<span class=\"Apple-style-span\""); + } + return markup; } -bool WebViewCore::focusIfFocusableAndNotTextInput(DOMSelection* selection, Node* node) +void WebViewCore::selectAt(int x, int y) { - // TODO (svetoslavganov): Synchronize Android and WebKit focus - if (node->isFocusable()) { - WebCore::RenderObject* renderer = node->renderer(); - if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) { - // restore the selection after focus workaround for - // the FIXME in Element.cpp#updateFocusAppearance - ExceptionCode ec = 0; - PassRefPtr<Range> rangeRef = selection->getRangeAt(0, ec); - moveFocus(m_mainFrame, node); - if (rangeRef) - selection->addRange(rangeRef.get()); - return true; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_selectAt, + x, y); + checkException(env); +} + +Node* WebViewCore::getFirstIntermediaryInputOrButton(Node* fromNode, Node* toNode) +{ + // do bidirectional traversal to catch the case in which + // the toNode is a descendant of a control but the fromNode + // is not and the other way around + Node* currentNode = fromNode->traverseNextNode(); + while (currentNode && currentNode != toNode) { + if (isVisible(currentNode) + && (currentNode->hasTagName(WebCore::HTMLNames::inputTag) + || currentNode->hasTagName(WebCore::HTMLNames::buttonTag))) { + return currentNode; } + currentNode = currentNode->traverseNextNode(); } - return false; + currentNode = fromNode->traverseNextNodePostOrder(); + while (currentNode && currentNode != toNode) { + if (isVisible(currentNode) + && (currentNode->hasTagName(WebCore::HTMLNames::inputTag) + || currentNode->hasTagName(WebCore::HTMLNames::buttonTag))) { + return currentNode; + } + currentNode = currentNode->traverseNextNodePostOrder(); + } + return 0; } void WebViewCore::deleteSelection(int start, int end, int textGeneration) @@ -3659,17 +3928,6 @@ static jstring ModifySelection(JNIEnv *env, jobject obj, jint direction, jint gr return wtfStringToJstring(env, selectionString); } -static jstring MoveSelection(JNIEnv *env, jobject obj, jint framePtr, jint nodePtr) -{ -#ifdef ANDROID_INSTRUMENT - TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); -#endif - WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); - String selectionString = viewImpl->moveSelection((WebCore::Frame*) framePtr, - (WebCore::Node*) nodePtr); - return wtfStringToJstring(env, selectionString); -} - static void ReplaceTextfieldText(JNIEnv *env, jobject obj, jint oldStart, jint oldEnd, jstring replace, jint start, jint end, jint textGeneration) @@ -4258,8 +4516,6 @@ static JNINativeMethod gJavaWebViewCoreMethods[] = { (void*) SetSelection } , { "nativeModifySelection", "(II)Ljava/lang/String;", (void*) ModifySelection }, - { "nativeMoveSelection", "(II)Ljava/lang/String;", - (void*) MoveSelection }, { "nativeDeleteSelection", "(III)V", (void*) DeleteSelection } , { "nativeReplaceTextfieldText", "(IILjava/lang/String;III)V", diff --git a/WebKit/android/jni/WebViewCore.h b/WebKit/android/jni/WebViewCore.h index 611ee59..028a0c7 100644 --- a/WebKit/android/jni/WebViewCore.h +++ b/WebKit/android/jni/WebViewCore.h @@ -665,17 +665,19 @@ namespace android { // below are members responsible for accessibility support String modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int granularity); String modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int granularity); - Text* traverseNonEmptyNonWhitespaceTextNode(Node* fromNode, Node* toNode ,int direction); + Text* traverseVisibleNonEmptyNonWhitespaceTextNode(Node* fromNode, Node* toNode ,int direction); bool isVisible(Node* node); bool isHeading(Node* node); - bool isEmptyOrOnlyWhitespaceTextNode(Node* node); String formatMarkup(DOMSelection* selection); - void tryFocusInlineSelectionElement(DOMSelection* selection); - bool focusIfFocusableAndNotTextInput(DOMSelection* selection, Node* node); - bool setSelection(DOMSelection* selection, Text* textNode, int direction); - bool setSelection(DOMSelection* selection, Node* startNode, Node* endNode, int startOffset, int endOffset); + void selectAt(int x, int y); Node* m_currentNodeDomNavigationAxis; void scrollNodeIntoView(Frame* frame, Node* node); + bool isVisibleNonEmptyNonWhitespaceTextNode(Node* node); + String stripAppleSpanFromMarkup(String markup); + int rangeCompliantChildOffset(Node* parent, int offset); + Node* getFirstIntermediaryInputOrButton(Node* fromNode, Node* toNode); + bool isInputControl(Node* node); + #if ENABLE(TOUCH_EVENTS) bool m_forwardingTouchEvents; #endif diff --git a/WebKit/android/nav/WebView.cpp b/WebKit/android/nav/WebView.cpp index 28babc0..046b354 100644 --- a/WebKit/android/nav/WebView.cpp +++ b/WebKit/android/nav/WebView.cpp @@ -114,7 +114,6 @@ struct JavaGlue { jmethodID m_sendMoveFocus; jmethodID m_sendMoveMouse; jmethodID m_sendMoveMouseIfLatest; - jmethodID m_sendMoveSelection; jmethodID m_sendMotionUp; jmethodID m_domChangedFocus; jmethodID m_getScaledMaxXScroll; @@ -149,7 +148,6 @@ WebView(JNIEnv* env, jobject javaWebView, int viewImpl) : m_javaGlue.m_sendMoveFocus = GetJMethod(env, clazz, "sendMoveFocus", "(II)V"); m_javaGlue.m_sendMoveMouse = GetJMethod(env, clazz, "sendMoveMouse", "(IIII)V"); m_javaGlue.m_sendMoveMouseIfLatest = GetJMethod(env, clazz, "sendMoveMouseIfLatest", "(Z)V"); - m_javaGlue.m_sendMoveSelection = GetJMethod(env, clazz, "sendMoveSelection", "(II)V"); m_javaGlue.m_sendMotionUp = GetJMethod(env, clazz, "sendMotionUp", "(IIIIII)V"); m_javaGlue.m_domChangedFocus = GetJMethod(env, clazz, "domChangedFocus", "()V"); m_javaGlue.m_getScaledMaxXScroll = GetJMethod(env, clazz, "getScaledMaxXScroll", "()I"); @@ -847,8 +845,6 @@ bool moveCursor(int keyCode, int count, bool ignoreScroll) bool clearTextEntry = cachedNode != focus && focus && cachedNode->nodePointer() != focus->nodePointer() && focus->isTextInput(); sendMoveMouseIfLatest(clearTextEntry); - sendMoveSelection((WebCore::Frame*) cachedFrame->framePointer(), - (WebCore::Node*) cachedNode->nodePointer()); } else { int docHeight = root->documentHeight(); int docWidth = root->documentWidth(); @@ -910,7 +906,6 @@ void selectBestAt(const WebCore::IntRect& rect) if (!root) return; const CachedNode* node = findAt(root, rect, &frame, &rx, &ry); - if (!node) { DBG_NAV_LOGD("no nodes found root=%p", root); m_viewImpl->m_hasCursorBounds = false; @@ -928,8 +923,6 @@ void selectBestAt(const WebCore::IntRect& rect) sendMoveMouseIfLatest(false); if (!node) return; - sendMoveSelection((WebCore::Frame*) frame->framePointer(), - (WebCore::Node*) node->nodePointer()); } const CachedNode* m_cacheHitNode; @@ -1217,15 +1210,6 @@ void sendMoveMouseIfLatest(bool clearTextEntry) checkException(env); } -void sendMoveSelection(WebCore::Frame* frame, WebCore::Node* node) -{ - DBG_NAV_LOGD("framePtr=%p nodePtr=%p x=%d y=%d", frame, node); - JNIEnv* env = JSC::Bindings::getJNIEnv(); - env->CallVoidMethod(m_javaGlue.object(env).get(), - m_javaGlue.m_sendMoveSelection, (jint) frame, (jint) node); - checkException(env); -} - void sendMotionUp( WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y, int scrollY) { @@ -1960,6 +1944,16 @@ static void nativeSelectBestAt(JNIEnv *env, jobject obj, jobject jrect) view->selectBestAt(rect); } +static void nativeSelectAt(JNIEnv *env, jobject obj, jint x, jint y) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WebCore::IntRect rect = IntRect(x, y , 1, 1); + view->selectBestAt(rect); + if (view->hasCursorNode()) + view->showCursorUntimed(); +} + static jobject nativeLayerBounds(JNIEnv* env, jobject obj, jint jlayer) { SkRect r; @@ -2516,6 +2510,8 @@ static JNINativeMethod gJavaWebViewMethods[] = { (void*) nativeSelectAll }, { "nativeSelectBestAt", "(Landroid/graphics/Rect;)V", (void*) nativeSelectBestAt }, + { "nativeSelectAt", "(II)V", + (void*) nativeSelectAt }, { "nativeSelectionX", "()I", (void*) nativeSelectionX }, { "nativeSelectionY", "()I", |