Title: [196352] trunk
Revision
196352
Author
n_w...@apple.com
Date
2016-02-09 18:33:04 -0800 (Tue, 09 Feb 2016)

Log Message

AX: Implement word related text marker functions using TextIterator
https://bugs.webkit.org/show_bug.cgi?id=153939
<rdar://problem/24269605>

Reviewed by Chris Fleizach.

Source/WebCore:

Using CharacterOffset to implement word related text marker calls. Reused
logic from previousBoundary and nextBoundary in VisibleUnits class.

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

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::traverseToOffsetInRange):
(WebCore::AXObjectCache::rangeForNodeContents):
(WebCore::isReplacedNodeOrBR):
(WebCore::characterOffsetsInOrder):
(WebCore::resetNodeAndOffsetForReplacedNode):
(WebCore::setRangeStartOrEndWithCharacterOffset):
(WebCore::AXObjectCache::rangeForUnorderedCharacterOffsets):
(WebCore::AXObjectCache::setTextMarkerDataWithCharacterOffset):
(WebCore::AXObjectCache::startOrEndCharacterOffsetForRange):
(WebCore::AXObjectCache::startOrEndTextMarkerDataForRange):
(WebCore::AXObjectCache::characterOffsetForNodeAndOffset):
(WebCore::AXObjectCache::textMarkerDataForCharacterOffset):
(WebCore::AXObjectCache::previousNode):
(WebCore::AXObjectCache::visiblePositionFromCharacterOffset):
(WebCore::AXObjectCache::characterOffsetFromVisiblePosition):
(WebCore::AXObjectCache::textMarkerDataForVisiblePosition):
(WebCore::AXObjectCache::nextCharacterOffset):
(WebCore::AXObjectCache::previousCharacterOffset):
(WebCore::startWordBoundary):
(WebCore::endWordBoundary):
(WebCore::AXObjectCache::startCharacterOffsetOfWord):
(WebCore::AXObjectCache::endCharacterOffsetOfWord):
(WebCore::AXObjectCache::previousWordStartCharacterOffset):
(WebCore::AXObjectCache::nextWordEndCharacterOffset):
(WebCore::AXObjectCache::leftWordRange):
(WebCore::AXObjectCache::rightWordRange):
(WebCore::characterForCharacterOffset):
(WebCore::AXObjectCache::characterAfter):
(WebCore::AXObjectCache::characterBefore):
(WebCore::parentEditingBoundary):
(WebCore::AXObjectCache::nextWordBoundary):
(WebCore::AXObjectCache::previousWordBoundary):
(WebCore::AXObjectCache::rootAXEditableElement):
* accessibility/AXObjectCache.h:
(WebCore::AXObjectCache::removeNodeForUse):
(WebCore::AXObjectCache::isNodeInUse):
* accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
(-[WebAccessibilityObjectWrapper previousTextMarkerForNode:offset:]):
(-[WebAccessibilityObjectWrapper textMarkerForNode:offset:ignoreStart:]):
(-[WebAccessibilityObjectWrapper textMarkerForNode:offset:]):
(textMarkerForCharacterOffset):
(-[WebAccessibilityObjectWrapper accessibilityAttributeValue:forParameter:]):
* editing/VisibleUnits.cpp:
(WebCore::rightWordPosition):
(WebCore::prepend):
(WebCore::appendRepeatedCharacter):
(WebCore::suffixLengthForRange):
(WebCore::prefixLengthForRange):
(WebCore::backwardSearchForBoundaryWithTextIterator):
(WebCore::forwardSearchForBoundaryWithTextIterator):
(WebCore::previousBoundary):
(WebCore::nextBoundary):
* editing/VisibleUnits.h:

Tools:

* DumpRenderTree/AccessibilityUIElement.cpp:
(endTextMarkerCallback):
(leftWordTextMarkerRangeForTextMarkerCallback):
(rightWordTextMarkerRangeForTextMarkerCallback):
(previousWordStartTextMarkerForTextMarkerCallback):
(nextWordEndTextMarkerForTextMarkerCallback):
(setSelectedVisibleTextRangeCallback):
(AccessibilityUIElement::setSelectedVisibleTextRange):
(AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
(AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
(AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
(AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
(AccessibilityUIElement::getJSClass):
* DumpRenderTree/AccessibilityUIElement.h:
* DumpRenderTree/ios/AccessibilityUIElementIOS.mm:
(AccessibilityUIElement::setSelectedVisibleTextRange):
(AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
(AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
(AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
(AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
* DumpRenderTree/mac/AccessibilityUIElementMac.mm:
(AccessibilityUIElement::setSelectedVisibleTextRange):
(AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
(AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
(AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
(AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
(AccessibilityUIElement::supportedActions):
* WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp:
(WTR::AccessibilityUIElement::setBoolAttributeValue):
(WTR::AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
(WTR::AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
(WTR::AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
(WTR::AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
* WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h:
* WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl:
* WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm:
(WTR::AccessibilityUIElement::endTextMarker):
(WTR::AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
(WTR::AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
(WTR::AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
(WTR::AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
(WTR::AccessibilityUIElement::mathPostscriptsDescription):
* WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:
(WTR::AccessibilityUIElement::endTextMarker):
(WTR::AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
(WTR::AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
(WTR::AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
(WTR::AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
(WTR::_convertMathMultiscriptPairsToString):

LayoutTests:

* accessibility/mac/text-marker-word-nav-expected.txt: Added.
* accessibility/mac/text-marker-word-nav.html: Added.
* accessibility/text-marker/text-marker-previous-next-expected.txt:
* accessibility/text-marker/text-marker-previous-next.html:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (196351 => 196352)


--- trunk/LayoutTests/ChangeLog	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/LayoutTests/ChangeLog	2016-02-10 02:33:04 UTC (rev 196352)
@@ -1,3 +1,16 @@
+2016-02-09  Nan Wang  <n_w...@apple.com>
+
+        AX: Implement word related text marker functions using TextIterator
+        https://bugs.webkit.org/show_bug.cgi?id=153939
+        <rdar://problem/24269605>
+
+        Reviewed by Chris Fleizach.
+
+        * accessibility/mac/text-marker-word-nav-expected.txt: Added.
+        * accessibility/mac/text-marker-word-nav.html: Added.
+        * accessibility/text-marker/text-marker-previous-next-expected.txt:
+        * accessibility/text-marker/text-marker-previous-next.html:
+
 2016-02-09  Ryan Haddad  <ryanhad...@apple.com>
 
         Mark perf/adding-radio-buttons.html as flaky on ios-simulator

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


--- trunk/LayoutTests/accessibility/mac/text-marker-word-nav-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/accessibility/mac/text-marker-word-nav-expected.txt	2016-02-10 02:33:04 UTC (rev 196352)
@@ -0,0 +1,125 @@
+word1 test
+Thisislongword I'll try. Test Contenteditable is working.
+c d
+can't select
+巧克力是食物吗?
+كيف حالك؟
+both   spaces
+line breaks
+some
+text
+test audio file
+This tests that word navigation is working correctly.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Current character is: w
+Left word is: word1
+Right word is: word1
+Pre word start to next word end: word1
+
+Current character is: T
+Left word is: Thisislongword
+Right word is: Thisislongword
+Pre word start to next word end: Thisislongword
+
+Current character is:  
+Left word is:  
+Right word is: I'll
+Pre word start to next word end:  I'll
+
+Current character is:  
+Left word is:  
+Right word is: try
+Pre word start to next word end:  try
+
+Current character is: e
+Left word is: editable
+Right word is: editable
+Pre word start to next word end: editable
+
+Current character is:  
+Left word is:  
+Right word is: [ATTACHMENT]
+Pre word start to next word end:  [ATTACHMENT]
+
+Current character is: [ATTACHMENT]
+Left word is: [ATTACHMENT]
+Right word is: d
+Pre word start to next word end: [ATTACHMENT]d
+
+Current character is: c
+Left word is: can't
+Right word is: can't
+Pre word start to next word end: can't
+
+Current character is: 克
+Left word is: 巧克力
+Right word is: 巧克力
+Pre word start to next word end: 巧克力
+
+Current character is: 力
+Left word is: 巧克力
+Right word is: 是
+Pre word start to next word end: 巧克力是
+
+Current character is: 是
+Left word is: 是
+Right word is: 食物
+Pre word start to next word end: 是食物
+
+Current character is: ف
+Left word is: كيف
+Right word is:  
+Pre word start to next word end: كيف 
+
+Current character is:  
+Left word is:  
+Right word is: حالك
+Pre word start to next word end:  حالك
+
+Current character is: h
+Left word is: both
+Right word is:    
+Pre word start to next word end: both   
+
+Current character is: s
+Left word is: spaces
+Right word is: 'line break'
+Pre word start to next word end: spaces'line break'
+
+Current character is: e
+Left word is: some
+Right word is: 'line break'
+Pre word start to next word end: some'line break'
+
+Current character is: 'line break'
+Left word is: 'line break'
+Right word is: text
+Pre word start to next word end: 'line break'text
+
+Current character is:  
+Left word is:  
+Right word is: [ATTACHMENT]
+Pre word start to next word end:  [ATTACHMENT]
+
+Current character is: [ATTACHMENT]
+Left word is: [ATTACHMENT]
+Right word is: file
+Pre word start to next word end: [ATTACHMENT]file
+
+Current character is: f
+Left word is: file
+Right word is: file
+Pre word start to next word end: file
+
+Test going forward.
+End word: file
+
+Test going backwards.
+Start word: word1
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

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


--- trunk/LayoutTests/accessibility/mac/text-marker-word-nav.html	                        (rev 0)
+++ trunk/LayoutTests/accessibility/mac/text-marker-word-nav.html	2016-02-10 02:33:04 UTC (rev 196352)
@@ -0,0 +1,195 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<meta charset="utf-8"> 
+<script src=""
+</head>
+<style>
+.userselect { user-select: none; -webkit-user-select: none; }
+</style>
+<body id="body">
+
+<div id="text" tabindex="0">word1 test</div>
+<span id="span">Thisis</span>longword I<span>'ll try.</span>
+Test Content<span id="target" contenteditable="true">editable is working.</span>
+
+<div id="text2">
+c <img src="" aria-label="blah" style="background-color: #aaaaaa; width: 100px; height: 100px;">d
+</div>
+
+<div class="userselect" id="text3">can't select</div>
+
+<div id="text4">
+巧克力是食物吗?
+</div>
+<div id="text4a">
+كيف حالك؟
+</div>
+
+<pre id="text5">
+both   spaces
+line breaks
+</pre>
+
+<div id="text6">
+some<br>text
+</div>
+
+<div id="text7">
+test audio <audio controls><source src="" type="audio/mpeg"></audio>file
+</div>
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+
+    description("This tests that word navigation is working correctly.");
+    
+    if (window.accessibilityController) {
+        
+        var text = accessibilityController.accessibleElementById("text");
+        // Get the actual text node.
+        text = text.childAtIndex(0);
+        
+        // Check that we can get the word range. Land at "w" in "word1 test".
+        var textMarkerRange = text.textMarkerRangeForElement(text);
+        var startMarker = text.startTextMarkerForTextMarkerRange(textMarkerRange);
+        var currentMarker = advanceAndVerify(startMarker, 1, text);
+        
+        // Check the case with span
+        // At "T" in "Thisis", should return the word as "Thisislongword".
+        currentMarker = advanceAndVerify(currentMarker, 10, text);
+        // At " " before "I", the word should be "I'll".
+        currentMarker = advanceAndVerify(currentMarker, 14, text);
+        // At " " before "try", the word should excludes "."
+        currentMarker = advanceAndVerify(currentMarker, 5, text);
+        
+        // Check the case with contenteditable
+        // At "e" in "editable", the word should NOT include "Content" before it.
+        currentMarker = advanceAndVerify(currentMarker, 17, text);
+        
+        // Check the case with replaced node, the replaced node should be considered a word.
+        var text2 = accessibilityController.accessibleElementById("text2");
+        textMarkerRange = text2.textMarkerRangeForElement(text2);
+        currentMarker = text2.startTextMarkerForTextMarkerRange(textMarkerRange);
+        currentMarker = advanceAndVerify(currentMarker, 2, text2);
+        currentMarker = advanceAndVerify(currentMarker, 1, text2);
+        
+        // Check user-select:none is also working.
+        var text3 = accessibilityController.accessibleElementById("text3");
+        textMarkerRange = text3.textMarkerRangeForElement(text3);
+        currentMarker = text3.startTextMarkerForTextMarkerRange(textMarkerRange);
+        currentMarker = advanceAndVerify(currentMarker, 1, text3);
+        
+        // Check multi-language, Chinese here.
+        var text4 = accessibilityController.accessibleElementById("text4");
+        textMarkerRange = text4.textMarkerRangeForElement(text4);
+        currentMarker = text4.startTextMarkerForTextMarkerRange(textMarkerRange);
+        currentMarker = advanceAndVerify(currentMarker, 2, text4);
+        currentMarker = advanceAndVerify(currentMarker, 1, text4);
+        currentMarker = advanceAndVerify(currentMarker, 1, text4);
+        // And Arabic
+        var text4a = accessibilityController.accessibleElementById("text4a");
+        textMarkerRange = text4a.textMarkerRangeForElement(text4a);
+        currentMarker = text4a.startTextMarkerForTextMarkerRange(textMarkerRange);
+        currentMarker = advanceAndVerify(currentMarker, 3, text4a);
+        currentMarker = advanceAndVerify(currentMarker, 1, text4a);
+        
+        // Check text in pre tag with line breaks.
+        var text5 = accessibilityController.accessibleElementById("text5");
+        textMarkerRange = text5.textMarkerRangeForElement(text5);
+        currentMarker = text5.startTextMarkerForTextMarkerRange(textMarkerRange);
+        // At "h" in "both", right word should be "   ".
+        currentMarker = advanceAndVerify(currentMarker, 4, text5);
+        // At the end of first line, right word should be new line.
+        currentMarker = advanceAndVerify(currentMarker, 9, text5);
+        
+        // Check text with br tag in it.
+        var text6 = accessibilityController.accessibleElementById("text6");
+        textMarkerRange = text6.textMarkerRangeForElement(text6);
+        currentMarker = text6.startTextMarkerForTextMarkerRange(textMarkerRange);
+        currentMarker = advanceAndVerify(currentMarker, 4, text6);
+        currentMarker = advanceAndVerify(currentMarker, 1, text6);
+        
+        // Check <audio> element.
+        var text7 = accessibilityController.accessibleElementById("text7");
+        textMarkerRange = text7.textMarkerRangeForElement(text7);
+        currentMarker = text7.startTextMarkerForTextMarkerRange(textMarkerRange);
+        currentMarker = advanceAndVerify(currentMarker, 11, text7);
+        currentMarker = advanceAndVerify(currentMarker, 1, text7);
+        currentMarker = advanceAndVerify(currentMarker, 1, text7);
+        
+        // Check the word marker runs from start to end, and backwards.
+        // Make sure it won't hang.
+        verifyDocument(text);
+        
+        function advanceAndVerify(currentMarker, offset, obj) {
+            var previousMarker;
+            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 = replaceAttachmentInString(obj.stringForTextMarkerRange(markerRange));
+            debug("Current character is: " + currentCharacter);
+            
+            var previousWordRange = obj.leftWordTextMarkerRangeForTextMarker(textMarker);
+            var nextWordRange = obj.rightWordTextMarkerRangeForTextMarker(textMarker);
+            var preWord = replaceAttachmentInString(obj.stringForTextMarkerRange(previousWordRange));
+            var nextWord = replaceAttachmentInString(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 = replaceAttachmentInString(obj.stringForTextMarkerRange(preAndNextWordRange));
+            debug("Pre word start to next word end: " + preAndNextWord + "\n");
+        }
+        
+        function verifyDocument(obj) {
+            var start = obj.startTextMarker;
+            
+            // Going forward.
+            debug("Test going forward.");
+            var current = start;
+            var endWord = "file";
+            var currWord = "";
+            while(currWord != endWord) {
+                var nextWordRange = obj.rightWordTextMarkerRangeForTextMarker(current);
+                currWord = obj.stringForTextMarkerRange(nextWordRange);
+                current = obj.nextWordEndTextMarkerForTextMarker(current);
+            }
+            debug("End word: " + replaceAttachmentInString(currWord));
+            
+            // Going backwards.
+            debug("\nTest going backwards.");
+            var startWord = "word1";
+            currWord = ""; 
+            while(currWord != startWord) {
+                var previousWordRange = obj.leftWordTextMarkerRangeForTextMarker(current);
+                currWord = obj.stringForTextMarkerRange(previousWordRange);
+                current = obj.previousWordStartTextMarkerForTextMarker(current);
+            }
+            debug("Start word: " + replaceAttachmentInString(currWord));
+        }
+    }
+
+</script>
+
+<script src=""
+</body>
+</html>
\ No newline at end of file

Modified: trunk/LayoutTests/accessibility/text-marker/text-marker-previous-next-expected.txt (196351 => 196352)


--- trunk/LayoutTests/accessibility/text-marker/text-marker-previous-next-expected.txt	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/LayoutTests/accessibility/text-marker/text-marker-previous-next-expected.txt	2016-02-10 02:33:04 UTC (rev 196352)
@@ -12,9 +12,9 @@
 PASS text.textMarkerRangeLength(textMarkerRange) is 4
 PASS text.accessibilityElementForTextMarker(startMarker).isEqual(text) is true
 PASS text.accessibilityElementForTextMarker(endMarker).isEqual(text) is true
-PASS text.stringForTextMarkerRange(markerRange) is ''
+PASS text.stringForTextMarkerRange(markerRange) is newline
 PASS text.stringForTextMarkerRange(markerRange) is 't'
-PASS text.stringForTextMarkerRange(markerRange) is ''
+PASS text.stringForTextMarkerRange(markerRange) is newline
 PASS text.stringForTextMarkerRange(markerRange) is 't'
 PASS text2.textMarkerRangeLength(textMarkerRange2) is 5
 Object string for range: c [ATTACHMENT] d

Modified: trunk/LayoutTests/accessibility/text-marker/text-marker-previous-next.html (196351 => 196352)


--- trunk/LayoutTests/accessibility/text-marker/text-marker-previous-next.html	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/LayoutTests/accessibility/text-marker/text-marker-previous-next.html	2016-02-10 02:33:04 UTC (rev 196352)
@@ -50,7 +50,8 @@
             currentMarker = text.nextTextMarker(currentMarker);
         }
         markerRange = text.textMarkerRangeForMarkers(previousMarker, currentMarker);
-        shouldBe("text.stringForTextMarkerRange(markerRange)", "''");
+        var newline = '\n';
+        shouldBe("text.stringForTextMarkerRange(markerRange)", "newline");
         
         // Advance one more character, it will lande at "t" in "text1".
         previousMarker = currentMarker;
@@ -62,7 +63,7 @@
         previousMarker = text.previousTextMarker(previousMarker);
         currentMarker = text.previousTextMarker(currentMarker);
         markerRange = text.textMarkerRangeForMarkers(previousMarker, currentMarker);
-        shouldBe("text.stringForTextMarkerRange(markerRange)", "''");
+        shouldBe("text.stringForTextMarkerRange(markerRange)", "newline");
         
         // Traverse backwards one more character, it will land at the last character of "text".
         previousMarker = text.previousTextMarker(previousMarker);

Modified: trunk/Source/WebCore/ChangeLog (196351 => 196352)


--- trunk/Source/WebCore/ChangeLog	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Source/WebCore/ChangeLog	2016-02-10 02:33:04 UTC (rev 196352)
@@ -1,3 +1,71 @@
+2016-02-09  Nan Wang  <n_w...@apple.com>
+
+        AX: Implement word related text marker functions using TextIterator
+        https://bugs.webkit.org/show_bug.cgi?id=153939
+        <rdar://problem/24269605>
+
+        Reviewed by Chris Fleizach.
+
+        Using CharacterOffset to implement word related text marker calls. Reused
+        logic from previousBoundary and nextBoundary in VisibleUnits class.
+
+        Test: accessibility/mac/text-marker-word-nav.html
+
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::AXObjectCache::traverseToOffsetInRange):
+        (WebCore::AXObjectCache::rangeForNodeContents):
+        (WebCore::isReplacedNodeOrBR):
+        (WebCore::characterOffsetsInOrder):
+        (WebCore::resetNodeAndOffsetForReplacedNode):
+        (WebCore::setRangeStartOrEndWithCharacterOffset):
+        (WebCore::AXObjectCache::rangeForUnorderedCharacterOffsets):
+        (WebCore::AXObjectCache::setTextMarkerDataWithCharacterOffset):
+        (WebCore::AXObjectCache::startOrEndCharacterOffsetForRange):
+        (WebCore::AXObjectCache::startOrEndTextMarkerDataForRange):
+        (WebCore::AXObjectCache::characterOffsetForNodeAndOffset):
+        (WebCore::AXObjectCache::textMarkerDataForCharacterOffset):
+        (WebCore::AXObjectCache::previousNode):
+        (WebCore::AXObjectCache::visiblePositionFromCharacterOffset):
+        (WebCore::AXObjectCache::characterOffsetFromVisiblePosition):
+        (WebCore::AXObjectCache::textMarkerDataForVisiblePosition):
+        (WebCore::AXObjectCache::nextCharacterOffset):
+        (WebCore::AXObjectCache::previousCharacterOffset):
+        (WebCore::startWordBoundary):
+        (WebCore::endWordBoundary):
+        (WebCore::AXObjectCache::startCharacterOffsetOfWord):
+        (WebCore::AXObjectCache::endCharacterOffsetOfWord):
+        (WebCore::AXObjectCache::previousWordStartCharacterOffset):
+        (WebCore::AXObjectCache::nextWordEndCharacterOffset):
+        (WebCore::AXObjectCache::leftWordRange):
+        (WebCore::AXObjectCache::rightWordRange):
+        (WebCore::characterForCharacterOffset):
+        (WebCore::AXObjectCache::characterAfter):
+        (WebCore::AXObjectCache::characterBefore):
+        (WebCore::parentEditingBoundary):
+        (WebCore::AXObjectCache::nextWordBoundary):
+        (WebCore::AXObjectCache::previousWordBoundary):
+        (WebCore::AXObjectCache::rootAXEditableElement):
+        * accessibility/AXObjectCache.h:
+        (WebCore::AXObjectCache::removeNodeForUse):
+        (WebCore::AXObjectCache::isNodeInUse):
+        * accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
+        (-[WebAccessibilityObjectWrapper previousTextMarkerForNode:offset:]):
+        (-[WebAccessibilityObjectWrapper textMarkerForNode:offset:ignoreStart:]):
+        (-[WebAccessibilityObjectWrapper textMarkerForNode:offset:]):
+        (textMarkerForCharacterOffset):
+        (-[WebAccessibilityObjectWrapper accessibilityAttributeValue:forParameter:]):
+        * editing/VisibleUnits.cpp:
+        (WebCore::rightWordPosition):
+        (WebCore::prepend):
+        (WebCore::appendRepeatedCharacter):
+        (WebCore::suffixLengthForRange):
+        (WebCore::prefixLengthForRange):
+        (WebCore::backwardSearchForBoundaryWithTextIterator):
+        (WebCore::forwardSearchForBoundaryWithTextIterator):
+        (WebCore::previousBoundary):
+        (WebCore::nextBoundary):
+        * editing/VisibleUnits.h:
+
 2016-02-09  Daniel Bates  <daba...@apple.com>
 
         CSP: Extract helper classes into their own files

Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (196351 => 196352)


--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp	2016-02-10 02:33:04 UTC (rev 196352)
@@ -81,6 +81,7 @@
 #include "RenderTableRow.h"
 #include "RenderView.h"
 #include "ScrollView.h"
+#include "TextBoundaries.h"
 #include "TextIterator.h"
 #include <wtf/DataLog.h>
 
@@ -1470,6 +1471,7 @@
     
     for (; !iterator.atEnd(); iterator.advance()) {
         int currentLength = iterator.text().length();
+        bool hasReplacedNodeOrBR = false;
         
         Node& node = iterator.range()->startContainer();
         currentNode = &node;
@@ -1482,17 +1484,28 @@
                 offsetSoFar++;
                 currentLength++;
                 currentNode = childNode;
+                hasReplacedNodeOrBR = true;
             } else
                 continue;
         } else {
             // Ignore space, new line, tag node.
-            if (currentLength == 1 && isSpaceOrNewline(iterator.text()[0]))
-                continue;
+            if (currentLength == 1) {
+                if (isSpaceOrNewline(iterator.text()[0])) {
+                    // If the node has BR tag, we want to set the currentNode to it.
+                    int subOffset = iterator.range()->startOffset();
+                    Node* childNode = node.traverseToChildAt(subOffset);
+                    if (childNode && childNode->renderer() && childNode->renderer()->isBR()) {
+                        currentNode = childNode;
+                        hasReplacedNodeOrBR = true;
+                    } else
+                        continue;
+                }
+            }
             offsetSoFar += currentLength;
         }
 
         lastLength = currentLength;
-        lastStartOffset = iterator.range()->startOffset();
+        lastStartOffset = hasReplacedNodeOrBR ? 0 : iterator.range()->startOffset();
         
         // Break early if we have advanced enough characters.
         if (!toNodeEnd && offsetSoFar >= offset) {
@@ -1546,6 +1559,11 @@
     range->selectNodeContents(node, ec);
     return ec ? nullptr : range;
 }
+    
+static bool isReplacedNodeOrBR(Node* node)
+{
+    return node && (AccessibilityObject::replacedNodeNeedsCharacter(node) || node->hasTagName(brTag));
+}
 
 static bool characterOffsetsInOrder(const CharacterOffset& characterOffset1, const CharacterOffset& characterOffset2)
 {
@@ -1555,8 +1573,18 @@
     if (characterOffset1.node == characterOffset2.node)
         return characterOffset1.offset <= characterOffset2.offset;
     
-    RefPtr<Range> range1 = AXObjectCache::rangeForNodeContents(characterOffset1.node);
-    RefPtr<Range> range2 = AXObjectCache::rangeForNodeContents(characterOffset2.node);
+    Node* node1 = characterOffset1.node;
+    Node* node2 = characterOffset2.node;
+    if (!node1->offsetInCharacters() && !isReplacedNodeOrBR(node1))
+        node1 = node1->traverseToChildAt(characterOffset1.offset);
+    if (!node2->offsetInCharacters() && !isReplacedNodeOrBR(node2))
+        node2 = node2->traverseToChildAt(characterOffset2.offset);
+    
+    if (!node1 || !node2)
+        return false;
+    
+    RefPtr<Range> range1 = AXObjectCache::rangeForNodeContents(node1);
+    RefPtr<Range> range2 = AXObjectCache::rangeForNodeContents(node2);
     return range1->compareBoundaryPoints(Range::START_TO_START, range2.get(), IGNORE_EXCEPTION) <= 0;
 }
 
@@ -1572,9 +1600,26 @@
     return replacedNode->parentNode();
 }
 
-static bool isReplacedNodeOrBR(Node* node)
+static void setRangeStartOrEndWithCharacterOffset(RefPtr<Range> range, CharacterOffset& characterOffset, bool isStart, ExceptionCode& ec)
 {
-    return AccessibilityObject::replacedNodeNeedsCharacter(node) || node->hasTagName(brTag);
+    if (!range) {
+        ec = RangeError;
+        return;
+    }
+    if (characterOffset.isNull()) {
+        ec = TypeError;
+        return;
+    }
+    
+    int offset = characterOffset.startIndex + characterOffset.offset;
+    Node* node = characterOffset.node;
+    if (isReplacedNodeOrBR(node))
+        node = resetNodeAndOffsetForReplacedNode(node, offset, characterOffset.offset);
+    
+    if (isStart)
+        range->setStart(node, offset, ec);
+    else
+        range->setEnd(node, offset, ec);
 }
 
 RefPtr<Range> AXObjectCache::rangeForUnorderedCharacterOffsets(const CharacterOffset& characterOffset1, const CharacterOffset& characterOffset2)
@@ -1586,34 +1631,11 @@
     CharacterOffset startCharacterOffset = alreadyInOrder ? characterOffset1 : characterOffset2;
     CharacterOffset endCharacterOffset = alreadyInOrder ? characterOffset2 : characterOffset1;
     
-    int startOffset = startCharacterOffset.startIndex + startCharacterOffset.offset;
-    int endOffset = endCharacterOffset.startIndex + endCharacterOffset.offset;
-    Node* startNode = startCharacterOffset.node;
-    Node* endNode = endCharacterOffset.node;
-    
-    // Consider the case when the replaced node is at the start/end of the range.
-    bool startNodeIsReplacedOrBR = isReplacedNodeOrBR(startNode);
-    bool endNodeIsReplacedOrBR = isReplacedNodeOrBR(endNode);
-    if (startNodeIsReplacedOrBR || endNodeIsReplacedOrBR) {
-        // endOffset can be out of bounds sometimes if the node is a replaced node or has brTag and it has no children.
-        if (startNode == endNode && !startNode->hasChildNodes()) {
-            RefPtr<Range> nodeRange = AXObjectCache::rangeForNodeContents(startNode);
-            int nodeLength = TextIterator::rangeLength(nodeRange.get());
-            if (endCharacterOffset.offset > nodeLength)
-                endOffset = endCharacterOffset.startIndex + nodeLength;
-        } else {
-            if (startNodeIsReplacedOrBR)
-                startNode = resetNodeAndOffsetForReplacedNode(startNode, startOffset, startCharacterOffset.offset);
-            if (endNodeIsReplacedOrBR)
-                endNode = resetNodeAndOffsetForReplacedNode(startNode, endOffset, startCharacterOffset.offset);
-        }
-    }
-    
     RefPtr<Range> result = Range::create(m_document);
-    ExceptionCode ecStart = 0, ecEnd = 0;
-    result->setStart(startNode, startOffset, ecStart);
-    result->setEnd(endNode, endOffset, ecEnd);
-    if (ecStart || ecEnd)
+    ExceptionCode ec = 0;
+    setRangeStartOrEndWithCharacterOffset(result, startCharacterOffset, true, ec);
+    setRangeStartOrEndWithCharacterOffset(result, endCharacterOffset, false, ec);
+    if (ec)
         return nullptr;
     
     return result;
@@ -1635,7 +1657,7 @@
         return;
     
     // Convert to visible position.
-    VisiblePosition visiblePosition = visiblePositionFromCharacterOffset(obj.get(), characterOffset);
+    VisiblePosition visiblePosition = visiblePositionFromCharacterOffset(characterOffset);
     int vpOffset = 0;
     if (!visiblePosition.isNull()) {
         Position deepPos = visiblePosition.deepEquivalent();
@@ -1652,53 +1674,64 @@
     this->setNodeInUse(domNode);
 }
 
-void AXObjectCache::startOrEndTextMarkerDataForRange(TextMarkerData& textMarkerData, RefPtr<Range> range, bool isStart)
+CharacterOffset AXObjectCache::startOrEndCharacterOffsetForRange(RefPtr<Range> range, bool isStart)
 {
-    memset(&textMarkerData, 0, sizeof(TextMarkerData));
-    
     if (!range)
-        return;
+        return CharacterOffset();
     
     // If it's end text marker, we want to go to the end of the range, and stay within the range.
     bool stayWithinRange = !isStart;
     
+    RefPtr<Range> copyRange = range;
     // Change the start of the range, so the character offset starts from node beginning.
     int offset = 0;
-    Node* node = &range->startContainer();
+    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, false);
-        offset = std::max(range->startOffset() - nodeStartOffset.startIndex, 0);
-        range->setStart(node, nodeStartOffset.startIndex);
+        offset = std::max(copyRange->startOffset() - nodeStartOffset.startIndex, 0);
+        copyRange->setStart(node, nodeStartOffset.startIndex);
     }
     
-    CharacterOffset characterOffset = traverseToOffsetInRange(range, offset, !isStart, stayWithinRange);
-    setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset);
+    return traverseToOffsetInRange(copyRange, offset, !isStart, stayWithinRange);
 }
 
-void AXObjectCache::textMarkerDataForCharacterOffset(TextMarkerData& textMarkerData, Node& node, int offset, bool toNodeEnd)
+void AXObjectCache::startOrEndTextMarkerDataForRange(TextMarkerData& textMarkerData, RefPtr<Range> range, bool isStart)
 {
     memset(&textMarkerData, 0, sizeof(TextMarkerData));
     
+    CharacterOffset characterOffset = startOrEndCharacterOffsetForRange(range, isStart);
+    if (characterOffset.isNull())
+        return;
+    
+    setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset);
+}
+
+CharacterOffset AXObjectCache::characterOffsetForNodeAndOffset(Node& node, int offset, bool toNodeEnd, bool ignoreStart)
+{
     Node* domNode = &node;
     if (!domNode)
-        return;
+        return CharacterOffset();
     
-    // If offset <= 0, means we want to go to the previous node.
-    if (offset <= 0 && !toNodeEnd) {
+    // ignoreStart is used to determine if we should go to previous node or
+    // stay in current node when offset is 0.
+    if (!toNodeEnd && (offset < 0 || (!offset && ignoreStart))) {
         // Set the offset to the amount of characters we need to go backwards.
-        offset = - offset + 1;
-        while (offset > 0 && textMarkerData.characterOffset <= offset) {
-            offset -= textMarkerData.characterOffset;
+        offset = - offset;
+        CharacterOffset charOffset = CharacterOffset();
+        while (offset >= 0 && charOffset.offset <= offset) {
+            offset -= charOffset.offset;
             domNode = previousNode(domNode);
             if (domNode) {
-                textMarkerDataForCharacterOffset(textMarkerData, *domNode, 0, true);
-                offset--;
+                charOffset = characterOffsetForNodeAndOffset(*domNode, 0, true);
             } else
-                return;
+                return CharacterOffset();
+            if (!offset)
+                break;
         }
         if (offset > 0)
-            textMarkerDataForCharacterOffset(textMarkerData, *domNode, offset, false);
-        return;
+            charOffset = characterOffsetForNodeAndOffset(*charOffset.node, charOffset.offset - offset, false);
+        return charOffset;
     }
     
     RefPtr<Range> range = rangeForNodeContents(domNode);
@@ -1709,11 +1742,19 @@
     while (!characterOffset.isNull() && characterOffset.remaining() && !toNodeEnd) {
         domNode = nextNode(domNode);
         if (!domNode)
-            return;
+            return CharacterOffset();
         range = rangeForNodeContents(domNode);
         characterOffset = traverseToOffsetInRange(range, characterOffset.remaining(), toNodeEnd);
     }
     
+    return characterOffset;
+}
+
+void AXObjectCache::textMarkerDataForCharacterOffset(TextMarkerData& textMarkerData, Node& node, int offset, bool toNodeEnd, bool ignoreStart)
+{
+    memset(&textMarkerData, 0, sizeof(TextMarkerData));
+    
+    CharacterOffset characterOffset = characterOffsetForNodeAndOffset(node, offset, toNodeEnd, ignoreStart);
     setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset);
 }
 
@@ -1737,8 +1778,12 @@
     return NodeTraversal::previousSkippingChildren(*node);
 }
 
-VisiblePosition AXObjectCache::visiblePositionFromCharacterOffset(AccessibilityObject* obj, const CharacterOffset& characterOffset)
+VisiblePosition AXObjectCache::visiblePositionFromCharacterOffset(const CharacterOffset& characterOffset)
 {
+    if (characterOffset.isNull())
+        return VisiblePosition();
+    
+    RefPtr<AccessibilityObject> obj = this->getOrCreate(characterOffset.node);
     if (!obj)
         return VisiblePosition();
     
@@ -1752,13 +1797,20 @@
     return result;
 }
 
-CharacterOffset AXObjectCache::characterOffsetFromVisiblePosition(AccessibilityObject* obj, const VisiblePosition& visiblePos)
+CharacterOffset AXObjectCache::characterOffsetFromVisiblePosition(const VisiblePosition& visiblePos)
 {
+    if (visiblePos.isNull())
+        return CharacterOffset();
+    
+    Position deepPos = visiblePos.deepEquivalent();
+    Node* domNode = deepPos.deprecatedNode();
+    ASSERT(domNode);
+    
+    RefPtr<AccessibilityObject> obj = this->getOrCreate(domNode);
     if (!obj)
-        return 0;
+        return CharacterOffset();
     
     // Use nextVisiblePosition to calculate how many characters we need to traverse to the current position.
-    Position deepPos = visiblePos.deepEquivalent();
     VisiblePositionRange vpRange = obj->visiblePositionRange();
     VisiblePosition vp = vpRange.start;
     int characterOffset = 0;
@@ -1816,13 +1868,256 @@
     textMarkerData.affinity = visiblePos.affinity();
     
     // convert to character offset
-    CharacterOffset characterOffset = characterOffsetFromVisiblePosition(obj.get(), visiblePos);
+    CharacterOffset characterOffset = characterOffsetFromVisiblePosition(visiblePos);
     textMarkerData.characterOffset = characterOffset.offset;
     textMarkerData.characterStartIndex = characterOffset.startIndex;
     
     cache->setNodeInUse(domNode);
 }
 
+CharacterOffset AXObjectCache::nextCharacterOffset(const CharacterOffset& characterOffset)
+{
+    if (characterOffset.isNull())
+        return CharacterOffset();
+    
+    return characterOffsetForNodeAndOffset(*characterOffset.node, characterOffset.offset + 1);
+}
+
+CharacterOffset AXObjectCache::previousCharacterOffset(const CharacterOffset& characterOffset)
+{
+    if (characterOffset.isNull())
+        return CharacterOffset();
+    
+    return characterOffsetForNodeAndOffset(*characterOffset.node, characterOffset.offset - 1, false, false);
+}
+
+static unsigned startWordBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
+{
+    ASSERT(offset);
+    if (mayHaveMoreContext && !startOfLastWordBoundaryContext(text.substring(0, offset))) {
+        needMoreContext = true;
+        return 0;
+    }
+    needMoreContext = false;
+    int start, end;
+    U16_BACK_1(text, 0, offset);
+    findWordBoundary(text, offset, &start, &end);
+    return start;
+}
+
+static unsigned endWordBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
+{
+    ASSERT(offset <= text.length());
+    if (mayHaveMoreContext && endOfFirstWordBoundaryContext(text.substring(offset)) == text.length() - offset) {
+        needMoreContext = true;
+        return text.length();
+    }
+    needMoreContext = false;
+    int end;
+    findEndWordBoundary(text, offset, &end);
+    return end;
+}
+
+CharacterOffset AXObjectCache::startCharacterOffsetOfWord(const CharacterOffset& characterOffset, EWordSide side)
+{
+    if (characterOffset.isNull())
+        return CharacterOffset();
+    
+    CharacterOffset c = characterOffset;
+    if (side == RightWordIfOnBoundary) {
+        // FIXME: need to remove this when isEndOfParagraph is implemented for CharacterOffset.
+        VisiblePosition vp = visiblePositionFromCharacterOffset(c);
+        if (isEndOfParagraph(vp))
+            return c;
+        
+        c = nextCharacterOffset(characterOffset);
+        if (c.isNull())
+            return characterOffset;
+    }
+    
+    return previousWordBoundary(c, startWordBoundary);
+}
+
+CharacterOffset AXObjectCache::endCharacterOffsetOfWord(const CharacterOffset& characterOffset, EWordSide side)
+{
+    if (characterOffset.isNull())
+        return CharacterOffset();
+    
+    CharacterOffset c = characterOffset;
+    if (side == LeftWordIfOnBoundary) {
+        // FIXME: need to remove this when isStartOfParagraph is implemented for CharacterOffset.
+        VisiblePosition vp = visiblePositionFromCharacterOffset(c);
+        if (isStartOfParagraph(vp))
+            return c;
+        
+        c = previousCharacterOffset(characterOffset);
+        if (c.isNull())
+            return characterOffset;
+    }
+    
+    return nextWordBoundary(c, endWordBoundary);
+}
+
+CharacterOffset AXObjectCache::previousWordStartCharacterOffset(const CharacterOffset& characterOffset)
+{
+    if (characterOffset.isNull())
+        return CharacterOffset();
+    
+    CharacterOffset previousOffset = previousCharacterOffset(characterOffset);
+    if (previousOffset.isNull())
+        return CharacterOffset();
+    
+    return startCharacterOffsetOfWord(previousOffset, RightWordIfOnBoundary);
+}
+
+CharacterOffset AXObjectCache::nextWordEndCharacterOffset(const CharacterOffset& characterOffset)
+{
+    if (characterOffset.isNull())
+        return CharacterOffset();
+    
+    CharacterOffset nextOffset = nextCharacterOffset(characterOffset);
+    if (nextOffset.isNull())
+        return CharacterOffset();
+    
+    return endCharacterOffsetOfWord(nextOffset, LeftWordIfOnBoundary);
+}
+
+RefPtr<Range> AXObjectCache::leftWordRange(const CharacterOffset& characterOffset)
+{
+    CharacterOffset start = previousWordStartCharacterOffset(characterOffset);
+    CharacterOffset end = endCharacterOffsetOfWord(start);
+    return rangeForUnorderedCharacterOffsets(start, end);
+}
+
+RefPtr<Range> AXObjectCache::rightWordRange(const CharacterOffset& characterOffset)
+{
+    CharacterOffset start = startCharacterOffsetOfWord(characterOffset);
+    CharacterOffset end = nextWordEndCharacterOffset(start);
+    return rangeForUnorderedCharacterOffsets(start, end);
+}
+
+static UChar32 characterForCharacterOffset(const CharacterOffset& characterOffset)
+{
+    if (characterOffset.isNull() || !characterOffset.node->isTextNode())
+        return 0;
+    
+    UChar32 ch = 0;
+    unsigned offset = characterOffset.startIndex + characterOffset.offset;
+    if (offset < characterOffset.node->textContent().length())
+        U16_NEXT(characterOffset.node->textContent(), offset, characterOffset.node->textContent().length(), ch);
+    return ch;
+}
+
+UChar32 AXObjectCache::characterAfter(const CharacterOffset& characterOffset)
+{
+    return characterForCharacterOffset(nextCharacterOffset(characterOffset));
+}
+
+UChar32 AXObjectCache::characterBefore(const CharacterOffset& characterOffset)
+{
+    return characterForCharacterOffset(characterOffset);
+}
+    
+static Node* parentEditingBoundary(Node* node)
+{
+    if (!node)
+        return nullptr;
+    
+    Node* documentElement = node->document().documentElement();
+    if (!documentElement)
+        return nullptr;
+    
+    Node* boundary = node;
+    while (boundary != documentElement && boundary->nonShadowBoundaryParentNode() && node->hasEditableStyle() == boundary->parentNode()->hasEditableStyle())
+        boundary = boundary->nonShadowBoundaryParentNode();
+    
+    return boundary;
+}
+
+CharacterOffset AXObjectCache::nextWordBoundary(CharacterOffset& characterOffset, BoundarySearchFunction searchFunction)
+{
+    if (characterOffset.isNull())
+        return CharacterOffset();
+    
+    Node* boundary = parentEditingBoundary(characterOffset.node);
+    if (!boundary)
+        return CharacterOffset();
+    
+    RefPtr<Range> searchRange = rangeForNodeContents(boundary);
+    Vector<UChar, 1024> string;
+    unsigned prefixLength = 0;
+    
+    ExceptionCode ec = 0;
+    if (requiresContextForWordBoundary(characterAfter(characterOffset))) {
+        RefPtr<Range> backwardsScanRange(boundary->document().createRange());
+        setRangeStartOrEndWithCharacterOffset(backwardsScanRange, characterOffset, false, ec);
+        prefixLength = prefixLengthForRange(backwardsScanRange, string);
+    }
+    
+    setRangeStartOrEndWithCharacterOffset(searchRange, characterOffset, true, ec);
+    CharacterOffset end = startOrEndCharacterOffsetForRange(searchRange, false);
+    
+    ASSERT(!ec);
+    if (ec)
+        return CharacterOffset();
+    
+    TextIterator it(searchRange.get(), TextIteratorEmitsObjectReplacementCharacters);
+    unsigned next = forwardSearchForBoundaryWithTextIterator(it, string, prefixLength, searchFunction);
+    
+    if (it.atEnd() && next == string.size())
+        return end;
+    if (next > prefixLength)
+        return characterOffsetForNodeAndOffset(*characterOffset.node, characterOffset.offset + next - prefixLength);
+    
+    return characterOffset;
+}
+
+CharacterOffset AXObjectCache::previousWordBoundary(CharacterOffset& characterOffset, BoundarySearchFunction searchFunction)
+{
+    if (characterOffset.isNull())
+        return CharacterOffset();
+    
+    Node* boundary = parentEditingBoundary(characterOffset.node);
+    if (!boundary)
+        return CharacterOffset();
+    
+    RefPtr<Range> searchRange = rangeForNodeContents(boundary);
+    Vector<UChar, 1024> string;
+    unsigned suffixLength = 0;
+    
+    ExceptionCode ec = 0;
+    if (requiresContextForWordBoundary(characterBefore(characterOffset))) {
+        RefPtr<Range> forwardsScanRange(boundary->document().createRange());
+        forwardsScanRange->setEndAfter(boundary, ec);
+        setRangeStartOrEndWithCharacterOffset(forwardsScanRange, characterOffset, true, ec);
+        suffixLength = suffixLengthForRange(forwardsScanRange, string);
+    }
+    
+    setRangeStartOrEndWithCharacterOffset(searchRange, characterOffset, false, ec);
+    CharacterOffset start = startOrEndCharacterOffsetForRange(searchRange, true);
+    
+    ASSERT(!ec);
+    if (ec)
+        return CharacterOffset();
+    
+    SimplifiedBackwardsTextIterator it(*searchRange);
+    unsigned next = backwardSearchForBoundaryWithTextIterator(it, string, suffixLength, searchFunction);
+    
+    if (!next)
+        return it.atEnd() ? start : characterOffset;
+    
+    Node& node = it.atEnd() ? searchRange->startContainer() : it.range()->startContainer();
+    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;
+        return characterOffsetForNodeAndOffset(node, next, false);
+    }
+    
+    int characterCount = characterOffset.offset - (string.size() - suffixLength - next);
+    return characterOffsetForNodeAndOffset(*characterOffset.node, characterCount, false, false);
+}
+
 const Element* AXObjectCache::rootAXEditableElement(const Node* node)
 {
     const Element* result = node->rootEditableElement();

Modified: trunk/Source/WebCore/accessibility/AXObjectCache.h (196351 => 196352)


--- trunk/Source/WebCore/accessibility/AXObjectCache.h	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.h	2016-02-10 02:33:04 UTC (rev 196352)
@@ -30,6 +30,7 @@
 #include "AccessibilityObject.h"
 #include "Range.h"
 #include "Timer.h"
+#include "VisibleUnits.h"
 #include <limits.h>
 #include <wtf/Forward.h>
 #include <wtf/HashMap.h>
@@ -186,12 +187,20 @@
     void textMarkerDataForVisiblePosition(TextMarkerData&, const VisiblePosition&);
     VisiblePosition visiblePositionForTextMarkerData(TextMarkerData&);
     CharacterOffset characterOffsetForTextMarkerData(TextMarkerData&);
-    void textMarkerDataForCharacterOffset(TextMarkerData&, Node&, int, bool toNodeEnd = false);
+    void textMarkerDataForCharacterOffset(TextMarkerData&, Node&, int, bool toNodeEnd = false, bool ignoreStart = true);
     void startOrEndTextMarkerDataForRange(TextMarkerData&, RefPtr<Range>, bool);
     AccessibilityObject* accessibilityObjectForTextMarkerData(TextMarkerData&);
     RefPtr<Range> rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&);
     static RefPtr<Range> rangeForNodeContents(Node*);
     static int lengthForRange(Range*);
+    
+    // Word boundary
+    CharacterOffset startCharacterOffsetOfWord(const CharacterOffset&, EWordSide = RightWordIfOnBoundary);
+    CharacterOffset endCharacterOffsetOfWord(const CharacterOffset&, EWordSide = RightWordIfOnBoundary);
+    CharacterOffset nextWordEndCharacterOffset(const CharacterOffset&);
+    CharacterOffset previousWordStartCharacterOffset(const CharacterOffset&);
+    RefPtr<Range> leftWordRange(const CharacterOffset&);
+    RefPtr<Range> rightWordRange(const CharacterOffset&);
 
     enum AXNotification {
         AXActiveDescendantChanged,
@@ -283,12 +292,21 @@
     void removeNodeForUse(Node* n) { m_textMarkerNodes.remove(n); }
     bool isNodeInUse(Node* n) { return m_textMarkerNodes.contains(n); }
     
+    // CharacterOffset functions.
     Node* nextNode(Node*) const;
     Node* previousNode(Node*) const;
     CharacterOffset traverseToOffsetInRange(RefPtr<Range>, int, bool, bool stayWithinRange = false);
-    VisiblePosition visiblePositionFromCharacterOffset(AccessibilityObject*, const CharacterOffset&);
-    CharacterOffset characterOffsetFromVisiblePosition(AccessibilityObject*, const VisiblePosition&);
+    VisiblePosition visiblePositionFromCharacterOffset(const CharacterOffset&);
+    CharacterOffset characterOffsetFromVisiblePosition(const VisiblePosition&);
     void setTextMarkerDataWithCharacterOffset(TextMarkerData&, const CharacterOffset&);
+    UChar32 characterAfter(const CharacterOffset&);
+    UChar32 characterBefore(const CharacterOffset&);
+    CharacterOffset startOrEndCharacterOffsetForRange(RefPtr<Range>, bool);
+    CharacterOffset characterOffsetForNodeAndOffset(Node&, int, bool toNodeEnd = false, bool ignoreStart = true);
+    CharacterOffset nextCharacterOffset(const CharacterOffset&);
+    CharacterOffset previousCharacterOffset(const CharacterOffset&);
+    CharacterOffset previousWordBoundary(CharacterOffset&, BoundarySearchFunction);
+    CharacterOffset nextWordBoundary(CharacterOffset&, BoundarySearchFunction);
 
 private:
     AccessibilityObject* rootWebArea();

Modified: trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm (196351 => 196352)


--- trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm	2016-02-10 02:33:04 UTC (rev 196352)
@@ -858,12 +858,17 @@
     return textMarker;
 }
 
+- (id)textMarkerForNode:(Node&)node offset:(int)offset ignoreStart:(BOOL)ignoreStart
+{
+    return textMarkerForCharacterOffset(m_object->axObjectCache(), node, offset, false, ignoreStart);
+}
+
 - (id)textMarkerForNode:(Node&)node offset:(int)offset
 {
-    return textMarkerForCharacterOffset(m_object->axObjectCache(), node, offset);
+    return [self textMarkerForNode:node offset:offset ignoreStart:YES];
 }
 
-static id textMarkerForCharacterOffset(AXObjectCache* cache, Node& node, int offset, bool toNodeEnd = false)
+static id textMarkerForCharacterOffset(AXObjectCache* cache, Node& node, int offset, bool toNodeEnd, bool ignoreStart)
 {
     if (!cache)
         return nil;
@@ -873,7 +878,7 @@
         return nil;
     
     TextMarkerData textMarkerData;
-    cache->textMarkerDataForCharacterOffset(textMarkerData, node, offset, toNodeEnd);
+    cache->textMarkerDataForCharacterOffset(textMarkerData, node, offset, toNodeEnd, ignoreStart);
     if (!textMarkerData.axID && !textMarkerData.ignored)
         return nil;
     
@@ -4070,15 +4075,21 @@
     }
     
     if ([attribute isEqualToString:@"AXLeftWordTextMarkerRangeForTextMarker"]) {
-        VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
-        VisiblePositionRange vpRange = m_object->positionOfLeftWord(visiblePos);
-        return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
+        AXObjectCache* cache = m_object->axObjectCache();
+        if (!cache)
+            return nil;
+        CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
+        RefPtr<Range> range = cache->leftWordRange(characterOffset);
+        return [self textMarkerRangeFromRange:range];
     }
     
     if ([attribute isEqualToString:@"AXRightWordTextMarkerRangeForTextMarker"]) {
-        VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
-        VisiblePositionRange vpRange = m_object->positionOfRightWord(visiblePos);
-        return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
+        AXObjectCache* cache = m_object->axObjectCache();
+        if (!cache)
+            return nil;
+        CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
+        RefPtr<Range> range = cache->rightWordRange(characterOffset);
+        return [self textMarkerRangeFromRange:range];
     }
     
     if ([attribute isEqualToString:@"AXLeftLineTextMarkerRangeForTextMarker"]) {
@@ -4106,13 +4117,21 @@
     }
     
     if ([attribute isEqualToString:@"AXNextWordEndTextMarkerForTextMarker"]) {
-        VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
-        return [self textMarkerForVisiblePosition:m_object->nextWordEnd(visiblePos)];
+        AXObjectCache* cache = m_object->axObjectCache();
+        if (!cache)
+            return nil;
+        CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
+        CharacterOffset nextEnd = cache->nextWordEndCharacterOffset(characterOffset);
+        return [self textMarkerForNode:*nextEnd.node offset:nextEnd.offset];
     }
     
     if ([attribute isEqualToString:@"AXPreviousWordStartTextMarkerForTextMarker"]) {
-        VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
-        return [self textMarkerForVisiblePosition:m_object->previousWordStart(visiblePos)];
+        AXObjectCache* cache = m_object->axObjectCache();
+        if (!cache)
+            return nil;
+        CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
+        CharacterOffset previousStart = cache->previousWordStartCharacterOffset(characterOffset);
+        return [self textMarkerForNode:*previousStart.node offset:previousStart.offset ignoreStart:NO];
     }
     
     if ([attribute isEqualToString:@"AXNextLineEndTextMarkerForTextMarker"]) {

Modified: trunk/Source/WebCore/editing/VisibleUnits.cpp (196351 => 196352)


--- trunk/Source/WebCore/editing/VisibleUnits.cpp	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Source/WebCore/editing/VisibleUnits.cpp	2016-02-10 02:33:04 UTC (rev 196352)
@@ -439,10 +439,6 @@
 }
 
 
-enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext };
-
-typedef unsigned (*BoundarySearchFunction)(StringView, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext);
-
 static void prepend(Vector<UChar, 1024>& buffer, StringView string)
 {
     unsigned oldSize = buffer.size();
@@ -470,6 +466,99 @@
         buffer[oldSize + i] = character;
 }
 
+unsigned suffixLengthForRange(RefPtr<Range> forwardsScanRange, Vector<UChar, 1024>& string)
+{
+    unsigned suffixLength = 0;
+    TextIterator forwardsIterator(forwardsScanRange.get());
+    while (!forwardsIterator.atEnd()) {
+        StringView text = forwardsIterator.text();
+        unsigned i = endOfFirstWordBoundaryContext(text);
+        append(string, text.substring(0, i));
+        suffixLength += i;
+        if (i < text.length())
+            break;
+        forwardsIterator.advance();
+    }
+    return suffixLength;
+}
+
+unsigned prefixLengthForRange(RefPtr<Range> backwardsScanRange, Vector<UChar, 1024>& string)
+{
+    unsigned prefixLength = 0;
+    SimplifiedBackwardsTextIterator backwardsIterator(*backwardsScanRange);
+    while (!backwardsIterator.atEnd()) {
+        StringView text = backwardsIterator.text();
+        int i = startOfLastWordBoundaryContext(text);
+        prepend(string, text.substring(i));
+        prefixLength += text.length() - i;
+        if (i > 0)
+            break;
+        backwardsIterator.advance();
+    }
+    return prefixLength;
+}
+
+unsigned backwardSearchForBoundaryWithTextIterator(SimplifiedBackwardsTextIterator& it, Vector<UChar, 1024>& string, unsigned suffixLength, BoundarySearchFunction searchFunction)
+{
+    unsigned next = 0;
+    bool needMoreContext = false;
+    while (!it.atEnd()) {
+        bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style().textSecurity() != TSNONE;
+        // iterate to get chunks until the searchFunction returns a non-zero value.
+        if (!inTextSecurityMode)
+            prepend(string, it.text());
+        else {
+            // Treat bullets used in the text security mode as regular characters when looking for boundaries
+            prependRepeatedCharacter(string, 'x', it.text().length());
+        }
+        if (string.size() > suffixLength) {
+            next = searchFunction(StringView(string.data(), string.size()), string.size() - suffixLength, MayHaveMoreContext, needMoreContext);
+            if (next > 1) // FIXME: This is a work around for https://webkit.org/b/115070. We need to provide more contexts in general case.
+                break;
+        }
+        it.advance();
+    }
+    if (needMoreContext && string.size() > suffixLength) {
+        // The last search returned the beginning of the buffer and asked for more context,
+        // but there is no earlier text. Force a search with what's available.
+        next = searchFunction(StringView(string.data(), string.size()), string.size() - suffixLength, DontHaveMoreContext, needMoreContext);
+        ASSERT(!needMoreContext);
+    }
+    
+    return next;
+}
+
+unsigned forwardSearchForBoundaryWithTextIterator(TextIterator& it, Vector<UChar, 1024>& string, unsigned prefixLength, BoundarySearchFunction searchFunction)
+{
+    unsigned next = 0;
+    bool needMoreContext = false;
+    while (!it.atEnd()) {
+        bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style().textSecurity() != TSNONE;
+        // Keep asking the iterator for chunks until the search function
+        // returns an end value not equal to the length of the string passed to it.
+        if (!inTextSecurityMode)
+            append(string, it.text());
+        else {
+            // Treat bullets used in the text security mode as regular characters when looking for boundaries
+            appendRepeatedCharacter(string, 'x', it.text().length());
+        }
+        if (string.size() > prefixLength) {
+            next = searchFunction(StringView(string.data(), string.size()), prefixLength, MayHaveMoreContext, needMoreContext);
+            if (next != string.size())
+                break;
+        }
+        it.advance();
+    }
+    if (needMoreContext && string.size() > prefixLength) {
+        // The last search returned the end of the buffer and asked for more context,
+        // but there is no further text. Force a search with what's available.
+        next = searchFunction(StringView(string.data(), string.size()), prefixLength, DontHaveMoreContext, needMoreContext);
+        ASSERT(!needMoreContext);
+    }
+    
+    return next;
+}
+
 static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction)
 {
     Position pos = c.deepEquivalent();
@@ -490,16 +579,7 @@
         RefPtr<Range> forwardsScanRange(boundaryDocument.createRange());
         forwardsScanRange->setEndAfter(boundary, ec);
         forwardsScanRange->setStart(end.deprecatedNode(), end.deprecatedEditingOffset(), ec);
-        TextIterator forwardsIterator(forwardsScanRange.get());
-        while (!forwardsIterator.atEnd()) {
-            StringView text = forwardsIterator.text();
-            unsigned i = endOfFirstWordBoundaryContext(text);
-            append(string, text.substring(0, i));
-            suffixLength += i;
-            if (i < text.length())
-                break;
-            forwardsIterator.advance();
-        }
+        suffixLength = suffixLengthForRange(forwardsScanRange, string);
     }
 
     searchRange->setStart(start.deprecatedNode(), start.deprecatedEditingOffset(), ec);
@@ -510,30 +590,7 @@
         return VisiblePosition();
 
     SimplifiedBackwardsTextIterator it(*searchRange);
-    unsigned next = 0;
-    bool needMoreContext = false;
-    while (!it.atEnd()) {
-        bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style().textSecurity() != TSNONE;
-        // iterate to get chunks until the searchFunction returns a non-zero value.
-        if (!inTextSecurityMode) 
-            prepend(string, it.text());
-        else {
-            // Treat bullets used in the text security mode as regular characters when looking for boundaries
-            prependRepeatedCharacter(string, 'x', it.text().length());
-        }
-        if (string.size() > suffixLength) {
-            next = searchFunction(StringView(string.data(), string.size()), string.size() - suffixLength, MayHaveMoreContext, needMoreContext);
-            if (next > 1) // FIXME: This is a work around for https://webkit.org/b/115070. We need to provide more contexts in general case.
-                break;
-        }
-        it.advance();
-    }
-    if (needMoreContext && string.size() > suffixLength) {
-        // The last search returned the beginning of the buffer and asked for more context,
-        // but there is no earlier text. Force a search with what's available.
-        next = searchFunction(StringView(string.data(), string.size()), string.size() - suffixLength, DontHaveMoreContext, needMoreContext);
-        ASSERT(!needMoreContext);
-    }
+    unsigned next = backwardSearchForBoundaryWithTextIterator(it, string, suffixLength, searchFunction);
 
     if (!next)
         return VisiblePosition(it.atEnd() ? searchRange->startPosition() : pos, DOWNSTREAM);
@@ -568,46 +625,13 @@
     if (requiresContextForWordBoundary(c.characterAfter())) {
         RefPtr<Range> backwardsScanRange(boundaryDocument.createRange());
         backwardsScanRange->setEnd(start.deprecatedNode(), start.deprecatedEditingOffset(), IGNORE_EXCEPTION);
-        SimplifiedBackwardsTextIterator backwardsIterator(*backwardsScanRange);
-        while (!backwardsIterator.atEnd()) {
-            StringView text = backwardsIterator.text();
-            int i = startOfLastWordBoundaryContext(text);
-            prepend(string, text.substring(i));
-            prefixLength += text.length() - i;
-            if (i > 0)
-                break;
-            backwardsIterator.advance();
-        }
+        prefixLength = prefixLengthForRange(backwardsScanRange, string);
     }
 
     searchRange->selectNodeContents(boundary, IGNORE_EXCEPTION);
     searchRange->setStart(start.deprecatedNode(), start.deprecatedEditingOffset(), IGNORE_EXCEPTION);
     TextIterator it(searchRange.get(), TextIteratorEmitsCharactersBetweenAllVisiblePositions);
-    unsigned next = 0;
-    bool needMoreContext = false;
-    while (!it.atEnd()) {
-        bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style().textSecurity() != TSNONE;
-        // Keep asking the iterator for chunks until the search function
-        // returns an end value not equal to the length of the string passed to it.
-        if (!inTextSecurityMode)
-            append(string, it.text());
-        else {
-            // Treat bullets used in the text security mode as regular characters when looking for boundaries
-            appendRepeatedCharacter(string, 'x', it.text().length());
-        }
-        if (string.size() > prefixLength) {
-            next = searchFunction(StringView(string.data(), string.size()), prefixLength, MayHaveMoreContext, needMoreContext);
-            if (next != string.size())
-                break;
-        }
-        it.advance();
-    }
-    if (needMoreContext && string.size() > prefixLength) {
-        // The last search returned the end of the buffer and asked for more context,
-        // but there is no further text. Force a search with what's available.
-        next = searchFunction(StringView(string.data(), string.size()), prefixLength, DontHaveMoreContext, needMoreContext);
-        ASSERT(!needMoreContext);
-    }
+    unsigned next = forwardSearchForBoundaryWithTextIterator(it, string, prefixLength, searchFunction);
     
     if (it.atEnd() && next == string.size())
         pos = searchRange->endPosition();

Modified: trunk/Source/WebCore/editing/VisibleUnits.h (196351 => 196352)


--- trunk/Source/WebCore/editing/VisibleUnits.h	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Source/WebCore/editing/VisibleUnits.h	2016-02-10 02:33:04 UTC (rev 196352)
@@ -34,6 +34,8 @@
 
 class Node;
 class VisiblePosition;
+class SimplifiedBackwardsTextIterator;
+class TextIterator;
 
 enum EWordSide { RightWordIfOnBoundary = false, LeftWordIfOnBoundary = true };
 
@@ -107,6 +109,14 @@
 WEBCORE_EXPORT void charactersAroundPosition(const VisiblePosition&, UChar32& oneAfter, UChar32& oneBefore, UChar32& twoBefore);
 WEBCORE_EXPORT PassRefPtr<Range> rangeExpandedAroundPositionByCharacters(const VisiblePosition&, int numberOfCharactersToExpand);
 
+// helper function
+enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext };
+typedef unsigned (*BoundarySearchFunction)(StringView, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext);
+unsigned suffixLengthForRange(RefPtr<Range>, Vector<UChar, 1024>&);
+unsigned prefixLengthForRange(RefPtr<Range>, Vector<UChar, 1024>&);
+unsigned backwardSearchForBoundaryWithTextIterator(SimplifiedBackwardsTextIterator&, Vector<UChar, 1024>&, unsigned, BoundarySearchFunction);
+unsigned forwardSearchForBoundaryWithTextIterator(TextIterator&, Vector<UChar, 1024>&, unsigned, BoundarySearchFunction);
+
 } // namespace WebCore
 
 #endif // VisibleUnits_h

Modified: trunk/Tools/ChangeLog (196351 => 196352)


--- trunk/Tools/ChangeLog	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/ChangeLog	2016-02-10 02:33:04 UTC (rev 196352)
@@ -1,3 +1,61 @@
+2016-02-09  Nan Wang  <n_w...@apple.com>
+
+        AX: Implement word related text marker functions using TextIterator
+        https://bugs.webkit.org/show_bug.cgi?id=153939
+        <rdar://problem/24269605>
+
+        Reviewed by Chris Fleizach.
+
+        * DumpRenderTree/AccessibilityUIElement.cpp:
+        (endTextMarkerCallback):
+        (leftWordTextMarkerRangeForTextMarkerCallback):
+        (rightWordTextMarkerRangeForTextMarkerCallback):
+        (previousWordStartTextMarkerForTextMarkerCallback):
+        (nextWordEndTextMarkerForTextMarkerCallback):
+        (setSelectedVisibleTextRangeCallback):
+        (AccessibilityUIElement::setSelectedVisibleTextRange):
+        (AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
+        (AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
+        (AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
+        (AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
+        (AccessibilityUIElement::getJSClass):
+        * DumpRenderTree/AccessibilityUIElement.h:
+        * DumpRenderTree/ios/AccessibilityUIElementIOS.mm:
+        (AccessibilityUIElement::setSelectedVisibleTextRange):
+        (AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
+        (AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
+        (AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
+        (AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
+        * DumpRenderTree/mac/AccessibilityUIElementMac.mm:
+        (AccessibilityUIElement::setSelectedVisibleTextRange):
+        (AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
+        (AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
+        (AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
+        (AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
+        (AccessibilityUIElement::supportedActions):
+        * WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp:
+        (WTR::AccessibilityUIElement::setBoolAttributeValue):
+        (WTR::AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
+        (WTR::AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
+        (WTR::AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
+        (WTR::AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
+        * WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h:
+        * WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl:
+        * WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm:
+        (WTR::AccessibilityUIElement::endTextMarker):
+        (WTR::AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
+        (WTR::AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
+        (WTR::AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
+        (WTR::AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
+        (WTR::AccessibilityUIElement::mathPostscriptsDescription):
+        * WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:
+        (WTR::AccessibilityUIElement::endTextMarker):
+        (WTR::AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker):
+        (WTR::AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker):
+        (WTR::AccessibilityUIElement::previousWordStartTextMarkerForTextMarker):
+        (WTR::AccessibilityUIElement::nextWordEndTextMarkerForTextMarker):
+        (WTR::_convertMathMultiscriptPairsToString):
+
 2016-02-09  Csaba Osztrogonác  <o...@webkit.org>
 
         [EFL] Remove eail related cruft after r195725

Modified: trunk/Tools/DumpRenderTree/AccessibilityUIElement.cpp (196351 => 196352)


--- trunk/Tools/DumpRenderTree/AccessibilityUIElement.cpp	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/DumpRenderTree/AccessibilityUIElement.cpp	2016-02-10 02:33:04 UTC (rev 196352)
@@ -974,6 +974,42 @@
     return AccessibilityTextMarker::makeJSAccessibilityTextMarker(context, toAXElement(thisObject)->endTextMarker());
 }
 
+static JSValueRef leftWordTextMarkerRangeForTextMarkerCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    AccessibilityTextMarker* marker = nullptr;
+    if (argumentCount == 1)
+        marker = toTextMarker(JSValueToObject(context, arguments[0], exception));
+    
+    return AccessibilityTextMarkerRange::makeJSAccessibilityTextMarkerRange(context, toAXElement(thisObject)->leftWordTextMarkerRangeForTextMarker(marker));
+}
+
+static JSValueRef rightWordTextMarkerRangeForTextMarkerCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    AccessibilityTextMarker* marker = nullptr;
+    if (argumentCount == 1)
+        marker = toTextMarker(JSValueToObject(context, arguments[0], exception));
+    
+    return AccessibilityTextMarkerRange::makeJSAccessibilityTextMarkerRange(context, toAXElement(thisObject)->rightWordTextMarkerRangeForTextMarker(marker));
+}
+
+static JSValueRef previousWordStartTextMarkerForTextMarkerCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    AccessibilityTextMarker* marker = nullptr;
+    if (argumentCount == 1)
+        marker = toTextMarker(JSValueToObject(context, arguments[0], exception));
+    
+    return AccessibilityTextMarker::makeJSAccessibilityTextMarker(context, toAXElement(thisObject)->previousWordStartTextMarkerForTextMarker(marker));
+}
+
+static JSValueRef nextWordEndTextMarkerForTextMarkerCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    AccessibilityTextMarker* marker = nullptr;
+    if (argumentCount == 1)
+        marker = toTextMarker(JSValueToObject(context, arguments[0], exception));
+    
+    return AccessibilityTextMarker::makeJSAccessibilityTextMarker(context, toAXElement(thisObject)->nextWordEndTextMarkerForTextMarker(marker));
+}
+
 static JSValueRef setSelectedVisibleTextRangeCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
 {
     AccessibilityUIElement* uiElement = toAXElement(thisObject);
@@ -1564,6 +1600,26 @@
     return false;
 }
 
+AccessibilityTextMarkerRange AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*)
+{
+    return nullptr;
+}
+
+AccessibilityTextMarkerRange AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*)
+{
+    return nullptr;
+}
+
+AccessibilityTextMarker AccessibilityUIElement::previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker*)
+{
+    return nullptr;
+}
+
+AccessibilityTextMarker AccessibilityUIElement::nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker*)
+{
+    return nullptr;
+}
+
 #endif
 
 // Destruction
@@ -1744,6 +1800,10 @@
         { "nextTextMarker", nextTextMarkerCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "previousTextMarker", previousTextMarkerCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "stringForTextMarkerRange", stringForTextMarkerRangeCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "leftWordTextMarkerRangeForTextMarker", leftWordTextMarkerRangeForTextMarkerCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "rightWordTextMarkerRangeForTextMarker", rightWordTextMarkerRangeForTextMarkerCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "previousWordStartTextMarkerForTextMarker", previousWordStartTextMarkerForTextMarkerCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "nextWordEndTextMarkerForTextMarker", nextWordEndTextMarkerForTextMarkerCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setSelectedChild", setSelectedChildCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setSelectedChildAtIndex", setSelectedChildAtIndexCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "removeSelectionAtIndex", removeSelectionAtIndexCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },

Modified: trunk/Tools/DumpRenderTree/AccessibilityUIElement.h (196351 => 196352)


--- trunk/Tools/DumpRenderTree/AccessibilityUIElement.h	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/DumpRenderTree/AccessibilityUIElement.h	2016-02-10 02:33:04 UTC (rev 196352)
@@ -260,6 +260,10 @@
     AccessibilityUIElement accessibilityElementForTextMarker(AccessibilityTextMarker*);
     AccessibilityTextMarker startTextMarker();
     AccessibilityTextMarker endTextMarker();
+    AccessibilityTextMarkerRange leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*);
+    AccessibilityTextMarkerRange rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*);
+    AccessibilityTextMarker previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker*);
+    AccessibilityTextMarker nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker*);
     AccessibilityTextMarkerRange selectedTextMarkerRange();
     void resetSelectedTextMarkerRange();
     bool setSelectedVisibleTextRange(AccessibilityTextMarkerRange*);

Modified: trunk/Tools/DumpRenderTree/ios/AccessibilityUIElementIOS.mm (196351 => 196352)


--- trunk/Tools/DumpRenderTree/ios/AccessibilityUIElementIOS.mm	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/DumpRenderTree/ios/AccessibilityUIElementIOS.mm	2016-02-10 02:33:04 UTC (rev 196352)
@@ -550,6 +550,26 @@
     return false;
 }
 
+AccessibilityTextMarkerRange AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*)
+{
+    return nullptr;
+}
+
+AccessibilityTextMarkerRange AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*)
+{
+    return nullptr;
+}
+
+AccessibilityTextMarker AccessibilityUIElement::previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker*)
+{
+    return nullptr;
+}
+
+AccessibilityTextMarker AccessibilityUIElement::nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker*)
+{
+    return nullptr;
+}
+
 #endif // SUPPORTS_AX_TEXTMARKERS && PLATFORM(IOS)
 
 #pragma mark Unused

Modified: trunk/Tools/DumpRenderTree/mac/AccessibilityUIElementMac.mm (196351 => 196352)


--- trunk/Tools/DumpRenderTree/mac/AccessibilityUIElementMac.mm	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/DumpRenderTree/mac/AccessibilityUIElementMac.mm	2016-02-10 02:33:04 UTC (rev 196352)
@@ -1846,6 +1846,46 @@
     return true;
 }
 
+AccessibilityTextMarkerRange AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    id textMarkerRange = [m_element accessibilityAttributeValue:@"AXLeftWordTextMarkerRangeForTextMarker" forParameter:(id)textMarker->platformTextMarker()];
+    return AccessibilityTextMarkerRange(textMarkerRange);
+    END_AX_OBJC_EXCEPTIONS
+    
+    return nullptr;
+}
+
+AccessibilityTextMarkerRange AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    id textMarkerRange = [m_element accessibilityAttributeValue:@"AXRightWordTextMarkerRangeForTextMarker" forParameter:(id)textMarker->platformTextMarker()];
+    return AccessibilityTextMarkerRange(textMarkerRange);
+    END_AX_OBJC_EXCEPTIONS
+    
+    return nullptr;
+}
+
+AccessibilityTextMarker AccessibilityUIElement::previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    id previousTextMarker = [m_element accessibilityAttributeValue:@"AXPreviousWordStartTextMarkerForTextMarker" forParameter:(id)textMarker->platformTextMarker()];
+    return AccessibilityTextMarker(previousTextMarker);
+    END_AX_OBJC_EXCEPTIONS
+    
+    return nullptr;
+}
+
+AccessibilityTextMarker AccessibilityUIElement::nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    id nextTextMarker = [m_element accessibilityAttributeValue:@"AXNextWordEndTextMarkerForTextMarker" forParameter:(id)textMarker->platformTextMarker()];
+    return AccessibilityTextMarker(nextTextMarker);
+    END_AX_OBJC_EXCEPTIONS
+    
+    return nullptr;
+}
+
 #endif // SUPPORTS_AX_TEXTMARKERS && PLATFORM(MAC)
 
 JSStringRef AccessibilityUIElement::supportedActions()

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp (196351 => 196352)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp	2016-02-10 02:33:04 UTC (rev 196352)
@@ -244,5 +244,12 @@
 void AccessibilityUIElement::setBoolAttributeValue(JSStringRef, bool) { }
 #endif
 
+#if (!PLATFORM(MAC) && !PLATFORM(IOS)) || !HAVE(ACCESSIBILITY)
+PassRefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*) { return nullptr; }
+PassRefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*) { return nullptr; }
+PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker*) { return nullptr; }
+PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker*) { return nullptr; }
+#endif
+
 } // namespace WTR
 

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h (196351 => 196352)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h	2016-02-10 02:33:04 UTC (rev 196352)
@@ -251,6 +251,10 @@
     PassRefPtr<AccessibilityTextMarker> startTextMarker();
     PassRefPtr<AccessibilityTextMarker> endTextMarker();
     bool setSelectedVisibleTextRange(AccessibilityTextMarkerRange*);
+    PassRefPtr<AccessibilityTextMarkerRange> leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*);
+    PassRefPtr<AccessibilityTextMarkerRange> rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker*);
+    PassRefPtr<AccessibilityTextMarker> previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker*);
+    PassRefPtr<AccessibilityTextMarker> nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker*);
 
     // Returns an ordered list of supported actions for an element.
     JSRetainPtr<JSStringRef> supportedActions() const;

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl (196351 => 196352)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl	2016-02-10 02:33:04 UTC (rev 196352)
@@ -201,6 +201,10 @@
     readonly attribute AccessibilityTextMarker startTextMarker;
     readonly attribute AccessibilityTextMarker endTextMarker;
     boolean setSelectedVisibleTextRange(AccessibilityTextMarkerRange range);
+    AccessibilityTextMarkerRange leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker textMarker);
+    AccessibilityTextMarkerRange rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker textMarker);
+    AccessibilityTextMarker previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker textMarker);
+    AccessibilityTextMarker nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker textMarker);
 
     // Returns an ordered list of supported actions for an element.
     readonly attribute DOMString supportedActions;

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm (196351 => 196352)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm	2016-02-10 02:33:04 UTC (rev 196352)
@@ -1096,6 +1096,26 @@
     return nullptr;
 }
 
+PassRefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    return nullptr;
+}
+
+PassRefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    return nullptr;
+}
+
+PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    return nullptr;
+}
+
+PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    return nullptr;
+}
+
 JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPostscriptsDescription() const
 {
     return 0;

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm (196351 => 196352)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm	2016-02-10 02:29:38 UTC (rev 196351)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm	2016-02-10 02:33:04 UTC (rev 196352)
@@ -1873,6 +1873,46 @@
     return nullptr;
 }
 
+PassRefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    id textMarkerRange = [m_element accessibilityAttributeValue:@"AXLeftWordTextMarkerRangeForTextMarker" forParameter:(id)textMarker->platformTextMarker()];
+    return AccessibilityTextMarkerRange::create(textMarkerRange);
+    END_AX_OBJC_EXCEPTIONS
+    
+    return nullptr;
+}
+
+PassRefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    id textMarkerRange = [m_element accessibilityAttributeValue:@"AXRightWordTextMarkerRangeForTextMarker" forParameter:(id)textMarker->platformTextMarker()];
+    return AccessibilityTextMarkerRange::create(textMarkerRange);
+    END_AX_OBJC_EXCEPTIONS
+    
+    return nullptr;
+}
+
+PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    id previousWordStartMarker = [m_element accessibilityAttributeValue:@"AXPreviousWordStartTextMarkerForTextMarker" forParameter:(id)textMarker->platformTextMarker()];
+    return AccessibilityTextMarker::create(previousWordStartMarker);
+    END_AX_OBJC_EXCEPTIONS
+    
+    return nullptr;
+}
+
+PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    id nextWordEndMarker = [m_element accessibilityAttributeValue:@"AXNextWordEndTextMarkerForTextMarker" forParameter:(id)textMarker->platformTextMarker()];
+    return AccessibilityTextMarker::create(nextWordEndMarker);
+    END_AX_OBJC_EXCEPTIONS
+    
+    return nullptr;
+}
+
 static NSString *_convertMathMultiscriptPairsToString(NSArray *pairs)
 {
     __block NSMutableString *result = [NSMutableString string];
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to