Title: [203412] trunk
Revision
203412
Author
n_w...@apple.com
Date
2016-07-19 11:14:02 -0700 (Tue, 19 Jul 2016)

Log Message

AX: Incorrect behavior for word related text marker functions when there's collapsed whitespace
https://bugs.webkit.org/show_bug.cgi?id=159910

Reviewed by Chris Fleizach.

Source/WebCore:

We are getting a bad CharacterOffset when there's collapsed whitespace. Added a TraverseOptionValidateOffset
option to make sure we are getting the correct CharacterOffset based on the corresponding Range offset. And
fixed a word navigation issue based on that.

Test: accessibility/mac/text-marker-word-nav-collapsed-whitespace.html

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::traverseToOffsetInRange):
(WebCore::AXObjectCache::rangeForNodeContents):
(WebCore::AXObjectCache::startOrEndCharacterOffsetForRange):
(WebCore::AXObjectCache::characterOffsetFromVisiblePosition):
(WebCore::AXObjectCache::rightWordRange):
(WebCore::AXObjectCache::previousBoundary):
* accessibility/AXObjectCache.h:
(WebCore::AXObjectCache::isNodeInUse):

LayoutTests:

* accessibility/mac/text-marker-word-nav-collapsed-whitespace-expected.txt: Added.
* accessibility/mac/text-marker-word-nav-collapsed-whitespace.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (203411 => 203412)


--- trunk/LayoutTests/ChangeLog	2016-07-19 17:27:32 UTC (rev 203411)
+++ trunk/LayoutTests/ChangeLog	2016-07-19 18:14:02 UTC (rev 203412)
@@ -1,3 +1,13 @@
+2016-07-19  Nan Wang  <n_w...@apple.com>
+
+        AX: Incorrect behavior for word related text marker functions when there's collapsed whitespace
+        https://bugs.webkit.org/show_bug.cgi?id=159910
+
+        Reviewed by Chris Fleizach.
+
+        * accessibility/mac/text-marker-word-nav-collapsed-whitespace-expected.txt: Added.
+        * accessibility/mac/text-marker-word-nav-collapsed-whitespace.html: Added.
+
 2016-07-19  Youenn Fablet  <you...@apple.com>
 
         [Streams API] ReadableStreamController methods should throw if its stream is not readable

Added: trunk/LayoutTests/accessibility/mac/text-marker-word-nav-collapsed-whitespace-expected.txt (0 => 203412)


--- trunk/LayoutTests/accessibility/mac/text-marker-word-nav-collapsed-whitespace-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/accessibility/mac/text-marker-word-nav-collapsed-whitespace-expected.txt	2016-07-19 18:14:02 UTC (rev 203412)
@@ -0,0 +1,30 @@
+Test1 Test2 Test3
+This tests that word navigation is working correctly with collapsed whitespaces.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Current character is: 1
+Left word is: Test1
+Right word is:  
+Pre word start to next word end: Test1 
+
+Current character is:  
+Left word is:  
+Right word is: Test2
+Pre word start to next word end:  Test2
+
+Current character is: 2
+Left word is: Test2
+Right word is:  
+Pre word start to next word end: Test2 
+
+Current character is:  
+Left word is:  
+Right word is: Test3
+Pre word start to next word end:  Test3
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/accessibility/mac/text-marker-word-nav-collapsed-whitespace.html (0 => 203412)


--- trunk/LayoutTests/accessibility/mac/text-marker-word-nav-collapsed-whitespace.html	                        (rev 0)
+++ trunk/LayoutTests/accessibility/mac/text-marker-word-nav-collapsed-whitespace.html	2016-07-19 18:14:02 UTC (rev 203412)
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<meta charset="utf-8"> 
+<script src=""
+</head>
+<body id="body">
+
+<div id="text">
+Test1
+
+
+
+Test2   Test3
+</div>
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+
+    description("This tests that word navigation is working correctly with collapsed whitespaces.");
+    
+    if (window.accessibilityController) {
+        
+        var text = accessibilityController.accessibleElementById("text");
+        // Get the actual text node.
+        text = text.childAtIndex(0);
+        
+        // Check that we can get the second word "Test2"
+        var textMarkerRange = text.textMarkerRangeForElement(text);
+        var startMarker = text.startTextMarkerForTextMarkerRange(textMarkerRange);
+        var currentMarker = advanceAndVerify(startMarker, 5, text);
+        currentMarker = advanceAndVerify(currentMarker, 1, text);
+        
+        // Check that we can get the third word "Test3"
+        currentMarker = advanceAndVerify(currentMarker, 5, text);
+        currentMarker = advanceAndVerify(currentMarker, 1, text);
+        
+        function advanceAndVerify(currentMarker, offset, obj) {
+            var previousMarker = currentMarker;
+            for (var i = 0; i < offset; i++) {
+                previousMarker = currentMarker;
+                currentMarker = obj.nextTextMarker(previousMarker);
+            }
+            verifyWordRangeForTextMarker(previousMarker, currentMarker, obj);
+            return currentMarker;
+        }
+        
+        function replaceAttachmentInString(str) {
+            var newline = '\n';
+            str =  str.replace(String.fromCharCode(65532), "[ATTACHMENT]");
+            str = str.replace(newline, "'line break'");
+            return str;
+        }
+        
+        function verifyWordRangeForTextMarker(preMarker, textMarker, obj) {
+            var markerRange = obj.textMarkerRangeForMarkers(preMarker, textMarker);
+            var currentCharacter = obj.stringForTextMarkerRange(markerRange);
+            debug("Current character is: " + currentCharacter);
+            
+            var previousWordRange = obj.leftWordTextMarkerRangeForTextMarker(textMarker);
+            var nextWordRange = obj.rightWordTextMarkerRangeForTextMarker(textMarker);
+            var preWord = obj.stringForTextMarkerRange(previousWordRange);
+            var nextWord = obj.stringForTextMarkerRange(nextWordRange);
+            debug("Left word is: " + preWord);
+            debug("Right word is: " + nextWord);
+            
+            var preWordStart = obj.previousWordStartTextMarkerForTextMarker(textMarker);
+            var nextWordEnd = obj.nextWordEndTextMarkerForTextMarker(textMarker);
+            var preAndNextWordRange = obj.textMarkerRangeForMarkers(preWordStart, nextWordEnd);
+            var preAndNextWord = obj.stringForTextMarkerRange(preAndNextWordRange);
+            debug("Pre word start to next word end: " + preAndNextWord + "\n");
+        }
+    }
+
+</script>
+
+<script src=""
+</body>
+</html>
\ No newline at end of file

Modified: trunk/Source/WebCore/ChangeLog (203411 => 203412)


--- trunk/Source/WebCore/ChangeLog	2016-07-19 17:27:32 UTC (rev 203411)
+++ trunk/Source/WebCore/ChangeLog	2016-07-19 18:14:02 UTC (rev 203412)
@@ -1,3 +1,26 @@
+2016-07-19  Nan Wang  <n_w...@apple.com>
+
+        AX: Incorrect behavior for word related text marker functions when there's collapsed whitespace
+        https://bugs.webkit.org/show_bug.cgi?id=159910
+
+        Reviewed by Chris Fleizach.
+
+        We are getting a bad CharacterOffset when there's collapsed whitespace. Added a TraverseOptionValidateOffset
+        option to make sure we are getting the correct CharacterOffset based on the corresponding Range offset. And
+        fixed a word navigation issue based on that.
+
+        Test: accessibility/mac/text-marker-word-nav-collapsed-whitespace.html
+
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::AXObjectCache::traverseToOffsetInRange):
+        (WebCore::AXObjectCache::rangeForNodeContents):
+        (WebCore::AXObjectCache::startOrEndCharacterOffsetForRange):
+        (WebCore::AXObjectCache::characterOffsetFromVisiblePosition):
+        (WebCore::AXObjectCache::rightWordRange):
+        (WebCore::AXObjectCache::previousBoundary):
+        * accessibility/AXObjectCache.h:
+        (WebCore::AXObjectCache::isNodeInUse):
+
 2016-07-19  Youenn Fablet  <you...@apple.com>
 
         [Streams API] ReadableStreamController methods should throw if its stream is not readable

Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (203411 => 203412)


--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp	2016-07-19 17:27:32 UTC (rev 203411)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp	2016-07-19 18:14:02 UTC (rev 203412)
@@ -1503,6 +1503,7 @@
         return CharacterOffset();
     
     bool toNodeEnd = option & TraverseOptionToNodeEnd;
+    bool validateOffset = option & TraverseOptionValidateOffset;
     
     int offsetInCharacter = 0;
     int cumulativeOffset = 0;
@@ -1536,9 +1537,6 @@
     // Sometimes text contents in a node are splitted into several iterations, so that iterator.range()->startOffset()
     // might not be the correct character count. Here we use a previousNode object to keep track of that.
     Node* previousNode = nullptr;
-    // When text node has collapsed whitespaces, we need to treat it differently since text iterator
-    // will omit the collapsed spaces and make the offset inaccurate.
-    Node* collapsedWhitespaceNode = nullptr;
     for (; !iterator.atEnd(); iterator.advance()) {
         int currentLength = iterator.text().length();
         bool hasReplacedNodeOrBR = false;
@@ -1546,10 +1544,6 @@
         Node& node = iterator.range()->startContainer();
         currentNode = &node;
         
-        // The offset of node with collapsed whitespaces has been calcualted in the first iteration.
-        if (currentNode == collapsedWhitespaceNode)
-            continue;
-        
         // When currentLength == 0, we check if there's any replaced node.
         // If not, we skip the node with no length.
         if (!currentLength) {
@@ -1579,34 +1573,14 @@
                         continue;
                     }
                 }
-            } else if (currentNode->isTextNode() && currentNode->renderer()) {
-                // When there's collapsed whitespace, the text iterator will only count those spaces as one single space.
-                // Here we use the RenderText to get the actual length.
-                RenderText* renderedText = downcast<RenderText>(currentNode->renderer());
-                int currentStartOffset = iterator.range()->startOffset();
-                if (renderedText->style().isCollapsibleWhiteSpace(iterator.text()[currentLength - 1])  && currentLength + currentStartOffset != renderedText->caretMaxOffset()) {
-                    int appendLength = (&range->endContainer() == currentNode ? range->endOffset() : (int)renderedText->text()->length()) - currentStartOffset;
-                    lastStartOffset = currentStartOffset;
-                    cumulativeOffset += appendLength;
-                    lastLength = appendLength;
-                    
-                    // Break early if we have advanced enough characters.
-                    if (!toNodeEnd && cumulativeOffset >= offset) {
-                        offsetInCharacter = offset - (cumulativeOffset - lastLength);
-                        finished = true;
-                        break;
-                    }
-                    
-                    collapsedWhitespaceNode = currentNode;
-                    continue;
-                }
-                
             }
             cumulativeOffset += currentLength;
         }
 
-        if (currentNode == previousNode)
+        if (currentNode == previousNode) {
             lastLength += currentLength;
+            lastStartOffset = iterator.range()->endOffset() - lastLength;
+        }
         else {
             lastLength = currentLength;
             lastStartOffset = hasReplacedNodeOrBR ? 0 : iterator.range()->startOffset();
@@ -1613,8 +1587,9 @@
         }
         
         // Break early if we have advanced enough characters.
-        if (!toNodeEnd && cumulativeOffset >= offset) {
-            offsetInCharacter = offset - (cumulativeOffset - lastLength);
+        bool offsetLimitReached = validateOffset ? cumulativeOffset + lastStartOffset >= offset : cumulativeOffset >= offset;
+        if (!toNodeEnd && offsetLimitReached) {
+            offsetInCharacter = validateOffset ? std::max(offset - lastStartOffset, 0) : offset - (cumulativeOffset - lastLength);
             finished = true;
             break;
         }
@@ -1667,7 +1642,11 @@
         return nullptr;
     RefPtr<Range> range = Range::create(*document);
     ExceptionCode ec = 0;
-    range->selectNodeContents(*node, ec);
+    // For replaced node without children, we should incluce itself in the range.
+    if (AccessibilityObject::replacedNodeNeedsCharacter(node))
+        range->selectNode(*node, ec);
+    else
+        range->selectNodeContents(*node, ec);
     return ec ? nullptr : range;
 }
     
@@ -1811,15 +1790,20 @@
     // If it's end text marker, we want to go to the end of the range, and stay within the range.
     bool stayWithinRange = !isStart;
     
+    Node& endNode = range->endContainer();
+    if (endNode.offsetInCharacters() && !isStart)
+        return traverseToOffsetInRange(rangeForNodeContents(&endNode), range->endOffset(), TraverseOptionValidateOffset);
+    
     Ref<Range> copyRange = *range;
     // Change the start of the range, so the character offset starts from node beginning.
     int offset = 0;
     Node& node = copyRange->startContainer();
     if (node.offsetInCharacters()) {
-        copyRange = Range::create(range->ownerDocument(), &range->startContainer(), range->startOffset(), &range->endContainer(), range->endOffset());
-        CharacterOffset nodeStartOffset = traverseToOffsetInRange(rangeForNodeContents(&node), 0);
-        offset = std::max(copyRange->startOffset() - nodeStartOffset.startIndex, 0);
-        copyRange->setStart(node, nodeStartOffset.startIndex);
+        CharacterOffset nodeStartOffset = traverseToOffsetInRange(rangeForNodeContents(&node), range->startOffset(), TraverseOptionValidateOffset);
+        if (isStart)
+            return nodeStartOffset;
+        copyRange = Range::create(range->ownerDocument(), &range->startContainer(), 0, &range->endContainer(), range->endOffset());
+        offset += nodeStartOffset.offset;
     }
     
     return traverseToOffsetInRange(WTFMove(copyRange), offset, isStart ? TraverseOptionDefault : TraverseOptionToNodeEnd, stayWithinRange);
@@ -1984,7 +1968,7 @@
     ASSERT(domNode);
     
     if (domNode->offsetInCharacters())
-        return CharacterOffset(domNode, 0, deepPos.deprecatedEditingOffset(), 0);
+        return traverseToOffsetInRange(rangeForNodeContents(domNode), deepPos.deprecatedEditingOffset(), TraverseOptionValidateOffset);
     
     RefPtr<AccessibilityObject> obj = this->getOrCreate(domNode);
     if (!obj)
@@ -2186,7 +2170,7 @@
 
 RefPtr<Range> AXObjectCache::rightWordRange(const CharacterOffset& characterOffset)
 {
-    CharacterOffset start = startCharacterOffsetOfWord(characterOffset);
+    CharacterOffset start = startCharacterOffsetOfWord(characterOffset, RightWordIfOnBoundary);
     CharacterOffset end = endCharacterOffsetOfWord(start);
     return rangeForUnorderedCharacterOffsets(start, end);
 }
@@ -2319,10 +2303,18 @@
         return it.atEnd() ? start : characterOffset;
     
     Node& node = it.atEnd() ? searchRange->startContainer() : it.range()->startContainer();
+    
+    // SimplifiedBackwardsTextIterator ignores replaced elements.
+    if (AccessibilityObject::replacedNodeNeedsCharacter(characterOffset.node))
+        return characterOffsetForNodeAndOffset(*characterOffset.node, 0);
+    Node* nextSibling = node.nextSibling();
+    if (&node != characterOffset.node && AccessibilityObject::replacedNodeNeedsCharacter(nextSibling))
+        return startOrEndCharacterOffsetForRange(rangeForNodeContents(nextSibling), false);
+    
     if ((node.isTextNode() && static_cast<int>(next) <= node.maxCharacterOffset()) || (node.renderer() && node.renderer()->isBR() && !next)) {
         // The next variable contains a usable index into a text node
-        if (&node == characterOffset.node)
-            next -= characterOffset.startIndex;
+        if (node.isTextNode())
+            return traverseToOffsetInRange(rangeForNodeContents(&node), next, TraverseOptionValidateOffset);
         return characterOffsetForNodeAndOffset(node, next, TraverseOptionIncludeStart);
     }
     

Modified: trunk/Source/WebCore/accessibility/AXObjectCache.h (203411 => 203412)


--- trunk/Source/WebCore/accessibility/AXObjectCache.h	2016-07-19 17:27:32 UTC (rev 203411)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.h	2016-07-19 18:14:02 UTC (rev 203412)
@@ -348,7 +348,7 @@
     bool isNodeInUse(Node* n) { return m_textMarkerNodes.contains(n); }
     
     // CharacterOffset functions.
-    enum TraverseOption { TraverseOptionDefault = 1 << 0, TraverseOptionToNodeEnd = 1 << 1, TraverseOptionIncludeStart = 1 << 2 };
+    enum TraverseOption { TraverseOptionDefault = 1 << 0, TraverseOptionToNodeEnd = 1 << 1, TraverseOptionIncludeStart = 1 << 2, TraverseOptionValidateOffset = 1 << 3 };
     Node* nextNode(Node*) const;
     Node* previousNode(Node*) const;
     CharacterOffset traverseToOffsetInRange(RefPtr<Range>, int, TraverseOption = TraverseOptionDefault, bool stayWithinRange = false);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to