Diff
Modified: trunk/LayoutTests/ChangeLog (195239 => 195240)
--- trunk/LayoutTests/ChangeLog 2016-01-19 00:06:46 UTC (rev 195239)
+++ trunk/LayoutTests/ChangeLog 2016-01-19 00:56:13 UTC (rev 195240)
@@ -1,3 +1,15 @@
+2016-01-18 Nan Wang <n_w...@apple.com>
+
+ AX: [Mac] Implement next/previous text marker functions using TextIterator
+ https://bugs.webkit.org/show_bug.cgi?id=152728
+
+ Reviewed by Chris Fleizach.
+
+ * accessibility/mac/previous-next-text-marker-expected.txt: Added.
+ * accessibility/mac/previous-next-text-marker.html: Added.
+ * accessibility/mac/text-marker-with-user-select-none-expected.txt: Added.
+ * accessibility/mac/text-marker-with-user-select-none.html: Added.
+
2016-01-17 Simon Fraser <simon.fra...@apple.com>
More displaylist tests, and minor cleanup
Added: trunk/LayoutTests/accessibility/mac/previous-next-text-marker-expected.txt (0 => 195240)
--- trunk/LayoutTests/accessibility/mac/previous-next-text-marker-expected.txt (rev 0)
+++ trunk/LayoutTests/accessibility/mac/previous-next-text-marker-expected.txt 2016-01-19 00:56:13 UTC (rev 195240)
@@ -0,0 +1,37 @@
+text
+
+text1
+c d
+
+can't select
+This tests the next/previous text marker functions are implemented correctly.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS text.textMarkerRangeLength(textMarkerRange) is 4
+PASS text.accessibilityElementForTextMarker(startMarker).isEqual(text) is true
+PASS text.accessibilityElementForTextMarker(endMarker).isEqual(text) is true
+PASS element.stringValue is 'AXValue: '
+PASS element.stringValue is 'AXValue: text1'
+PASS element.stringValue is 'AXValue: '
+PASS element.stringValue is 'AXValue: text'
+PASS text2.textMarkerRangeLength(textMarkerRange2) is 5
+Object string for range: c [ATTACHMENT] d
+AXValue: c
+AXValue: c
+AXValue:
+AXValue: d
+AXValue:
+AXValue: c
+AXValue: c
+AXValue: text1
+PASS text3.stringForTextMarkerRange(markerRange) is 'ect'
+PASS text3.stringForTextMarkerRange(markerRange) is 'sel'
+PASS !psw.accessibilityElementForTextMarker(start) is true
+PASS text2.accessibilityElementForTextMarker(currentMarker).isEqual(text3) is true
+PASS text2.accessibilityElementForTextMarker(currentMarker).isEqual(text2.childAtIndex(2)) is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/accessibility/mac/previous-next-text-marker.html (0 => 195240)
--- trunk/LayoutTests/accessibility/mac/previous-next-text-marker.html (rev 0)
+++ trunk/LayoutTests/accessibility/mac/previous-next-text-marker.html 2016-01-19 00:56:13 UTC (rev 195240)
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+</head>
+<style>
+.userselect { user-select: none; -webkit-user-select: none; }
+</style>
+<body id="body">
+
+<div id="text" tabindex="0">text</div>
+<br>
+text1
+
+<div id="text2">
+c <img src="" aria-label="blah" style="background-color: #aaaaaa; width: 100px; height: 100px;"> d
+</div>
+
+<input type="password" id="psw">
+
+<div class="userselect" id="text3">can't select</div>
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+
+ description("This tests the next/previous text marker functions are implemented correctly.");
+
+ if (window.accessibilityController) {
+
+ var text = accessibilityController.accessibleElementById("text");
+ // Get the actual text node.
+ text = text.childAtIndex(0);
+
+ // Check that we can get the start/end marker for this range.
+ var textMarkerRange = text.textMarkerRangeForElement(text);
+ shouldBe("text.textMarkerRangeLength(textMarkerRange)", "4");
+
+ var startMarker = text.startTextMarkerForTextMarkerRange(textMarkerRange);
+ var endMarker = text.endTextMarkerForTextMarkerRange(textMarkerRange);
+ shouldBeTrue("text.accessibilityElementForTextMarker(startMarker).isEqual(text)");
+ shouldBeTrue("text.accessibilityElementForTextMarker(endMarker).isEqual(text)");
+
+ // Check next text marker. (Advance 5 characters, it will land at <br>.)
+ var currentMarker = startMarker;
+ for (var i = 0; i < 5; i++) {
+ currentMarker = text.nextTextMarker(currentMarker);
+ }
+ var element = text.accessibilityElementForTextMarker(currentMarker);
+ shouldBe("element.stringValue", "'AXValue: '");
+
+ // Advance one more character, it will lande at "t" in "text1".
+ currentMarker = text.nextTextMarker(currentMarker);
+ element = text.accessibilityElementForTextMarker(currentMarker);
+ shouldBe("element.stringValue", "'AXValue: text1'");
+
+ // Check previous text marker. (Traverse backwards one character, it will land at <br>.)
+ currentMarker = text.previousTextMarker(currentMarker);
+ element = text.accessibilityElementForTextMarker(currentMarker);
+ shouldBe("element.stringValue", "'AXValue: '");
+
+ // Traverse backwards one more character, it will land at the last character of "text".
+ currentMarker = text.previousTextMarker(currentMarker);
+ element = text.accessibilityElementForTextMarker(currentMarker);
+ shouldBe("element.stringValue", "'AXValue: text'");
+
+ // Check the case with replaced node
+ var text2 = accessibilityController.accessibleElementById("text2");
+ var textMarkerRange2 = text2.textMarkerRangeForElement(text2);
+ shouldBe("text2.textMarkerRangeLength(textMarkerRange2)", "5");
+ var str = text2.stringForTextMarkerRange(textMarkerRange2).replace(String.fromCharCode(65532), "[ATTACHMENT]");
+ debug("Object string for range: " + str);
+
+ currentMarker = text2.startTextMarkerForTextMarkerRange(textMarkerRange2);
+ // Advance 4 characters, it will land at first character of " d".
+ for (var i = 0; i < 4; i++) {
+ currentMarker = text2.nextTextMarker(currentMarker);
+ element = text2.accessibilityElementForTextMarker(currentMarker);
+ debug(element.stringValue);
+ }
+
+ // Traverse backwards 4 characters, it will land at the last character of "text1".
+ for (var i = 0; i < 4; i++) {
+ currentMarker = text2.previousTextMarker(currentMarker);
+ element = text2.accessibilityElementForTextMarker(currentMarker);
+ debug(element.stringValue);
+ }
+
+ // Check the case with user-select:none, nextTextMarker/previousTextMarker should still work.
+ var text3 = accessibilityController.accessibleElementById("text3");
+ text3 = text3.childAtIndex(0);
+ // Advance to land at user-select:none node.
+ var marker1, marker2;
+ for (var i = 0; i < 17; i++) {
+ currentMarker = text3.nextTextMarker(currentMarker);
+ // i == 13, it should land on "e", and i == 16, it should land on "t"
+ if (i == 13) {
+ marker1 = currentMarker;
+ }
+ }
+ marker2 = currentMarker;
+ var markerRange = text3.textMarkerRangeForMarkers(marker1, marker2);
+ shouldBe("text3.stringForTextMarkerRange(markerRange)", "'ect'");
+ // Iterate backwards the second marker for 6 characters, the range should be "sel"
+ for (var i = 0; i < 6; i++) {
+ currentMarker = text3.previousTextMarker(currentMarker);
+ }
+ marker2 = currentMarker;
+ markerRange = text3.textMarkerRangeForMarkers(marker1, marker2);
+ shouldBe("text3.stringForTextMarkerRange(markerRange)", "'sel'");
+
+ // Check the case with password field.
+ var psw = accessibilityController.accessibleElementById("psw");
+ var textMarkerRange3 = psw.textMarkerRangeForElement(psw);
+ var start = psw.startTextMarkerForTextMarkerRange(textMarkerRange3);
+ shouldBeTrue("!psw.accessibilityElementForTextMarker(start)");
+
+ // Check next/previous text marker call will skip password field
+ // We start from text2 and advance 6 characters, it should skip the password field and land on text3.
+ currentMarker = text2.startTextMarkerForTextMarkerRange(textMarkerRange2);
+ for (var i = 0; i < 6; i++) {
+ currentMarker = text2.nextTextMarker(currentMarker);
+ }
+ shouldBeTrue("text2.accessibilityElementForTextMarker(currentMarker).isEqual(text3)");
+ // Check previous text marker, it should land on " d" node.
+ currentMarker = text2.previousTextMarker(currentMarker);
+ shouldBeTrue("text2.accessibilityElementForTextMarker(currentMarker).isEqual(text2.childAtIndex(2))");
+
+ }
+
+</script>
+
+<script src=""
+</body>
+</html>
\ No newline at end of file
Added: trunk/LayoutTests/accessibility/mac/text-marker-with-user-select-none-expected.txt (0 => 195240)
--- trunk/LayoutTests/accessibility/mac/text-marker-with-user-select-none-expected.txt (rev 0)
+++ trunk/LayoutTests/accessibility/mac/text-marker-with-user-select-none-expected.txt 2016-01-19 00:56:13 UTC (rev 195240)
@@ -0,0 +1,16 @@
+hello test world test hello
+link to here
+test
+This tests that accessibility text markers still work even when user-select:none is set.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS textElement.textMarkerRangeLength(textMarkerRange) is 45
+PASS text is "hello test world test hello\nlink to here\ntest"
+PASS text is 'h'
+PASS element.isEqual(textElement.childAtIndex(0)) is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/accessibility/mac/text-marker-with-user-select-none.html (0 => 195240)
--- trunk/LayoutTests/accessibility/mac/text-marker-with-user-select-none.html (rev 0)
+++ trunk/LayoutTests/accessibility/mac/text-marker-with-user-select-none.html 2016-01-19 00:56:13 UTC (rev 195240)
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+</head>
+<body id="body">
+
+<div id="text" style="-webkit-user-select:none">
+
+hello test <b>world</b> test hello<br>
+<a href="" to <a href=""
+test
+
+</div>
+
+
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+
+ description("This tests that accessibility text markers still work even when user-select:none is set.");
+
+ if (window.accessibilityController) {
+
+ var textElement = accessibilityController.accessibleElementById("text");
+ var textMarkerRange = textElement.textMarkerRangeForElement(textElement);
+ shouldBe("textElement.textMarkerRangeLength(textMarkerRange)", "45");
+
+ var startMarker = textElement.startTextMarkerForTextMarkerRange(textMarkerRange);
+ var endMarker = textElement.endTextMarkerForTextMarkerRange(textMarkerRange);
+ textMarkerRange = textElement.textMarkerRangeForMarkers(startMarker, endMarker);
+ var text = textElement.stringForTextMarkerRange(textMarkerRange);
+ shouldBeEqualToString("text", "hello test world test hello\nlink to here\ntest");
+
+ var nextMarker = textElement.nextTextMarker(startMarker);
+ textMarkerRange = textElement.textMarkerRangeForMarkers(startMarker, nextMarker);
+ text = textElement.stringForTextMarkerRange(textMarkerRange);
+ shouldBe("text", "'h'");
+ var element = textElement.accessibilityElementForTextMarker(nextMarker);
+ shouldBeTrue("element.isEqual(textElement.childAtIndex(0))");
+ }
+
+</script>
+
+<script src=""
+</body>
+</html>
\ No newline at end of file
Modified: trunk/Source/WebCore/ChangeLog (195239 => 195240)
--- trunk/Source/WebCore/ChangeLog 2016-01-19 00:06:46 UTC (rev 195239)
+++ trunk/Source/WebCore/ChangeLog 2016-01-19 00:56:13 UTC (rev 195240)
@@ -1,3 +1,77 @@
+2016-01-18 Nan Wang <n_w...@apple.com>
+
+ AX: [Mac] Implement next/previous text marker functions using TextIterator
+ https://bugs.webkit.org/show_bug.cgi?id=152728
+
+ Reviewed by Chris Fleizach.
+
+ The existing AXTextMarker based calls are implemented using visible position, and that introduced
+ some bugs which make VoiceOver working incorrectly on Mac sometimes. Since TextIterator uses rendering
+ position, we tried to use it to refactor those AXTextMarker based calls.
+ In this patch, I implemented functions to navigate to previous/next text marker using Range and TextIterator.
+ Also added a conversion between visible position and character offset to make sure unconverted text marker
+ related functions are still working correctly.
+
+ Tests: accessibility/mac/previous-next-text-marker.html
+ accessibility/mac/text-marker-with-user-select-none.html
+
+ * accessibility/AXObjectCache.cpp:
+ (WebCore::AXObjectCache::visiblePositionForTextMarkerData):
+ (WebCore::AXObjectCache::traverseToOffsetInRange):
+ (WebCore::AXObjectCache::lengthForRange):
+ (WebCore::AXObjectCache::rangeForNodeContents):
+ (WebCore::characterOffsetsInOrder):
+ (WebCore::AXObjectCache::rangeForUnorderedCharacterOffsets):
+ (WebCore::AXObjectCache::setTextMarkerDataWithCharacterOffset):
+ (WebCore::AXObjectCache::startOrEndTextMarkerDataForRange):
+ (WebCore::AXObjectCache::textMarkerDataForCharacterOffset):
+ (WebCore::AXObjectCache::nextNode):
+ (WebCore::AXObjectCache::previousNode):
+ (WebCore::AXObjectCache::visiblePositionFromCharacterOffset):
+ (WebCore::AXObjectCache::characterOffsetFromVisiblePosition):
+ (WebCore::AXObjectCache::accessibilityObjectForTextMarkerData):
+ (WebCore::AXObjectCache::textMarkerDataForVisiblePosition):
+ * accessibility/AXObjectCache.h:
+ (WebCore::CharacterOffset::CharacterOffset):
+ (WebCore::CharacterOffset::remaining):
+ (WebCore::CharacterOffset::isNull):
+ (WebCore::AXObjectCache::setNodeInUse):
+ (WebCore::AXObjectCache::removeNodeForUse):
+ (WebCore::AXObjectCache::isNodeInUse):
+ * accessibility/AccessibilityObject.cpp:
+ (WebCore::AccessibilityObject::selectionRange):
+ (WebCore::AccessibilityObject::elementRange):
+ (WebCore::AccessibilityObject::selectText):
+ (WebCore::AccessibilityObject::lineRangeForPosition):
+ (WebCore::AccessibilityObject::replacedNodeNeedsCharacter):
+ (WebCore::renderListItemContainerForNode):
+ (WebCore::listMarkerTextForNode):
+ (WebCore::AccessibilityObject::listMarkerTextForNodeAndPosition):
+ (WebCore::AccessibilityObject::stringForRange):
+ (WebCore::AccessibilityObject::stringForVisiblePositionRange):
+ (WebCore::replacedNodeNeedsCharacter): Deleted.
+ * accessibility/AccessibilityObject.h:
+ (WebCore::AccessibilityObject::visiblePositionRange):
+ (WebCore::AccessibilityObject::visiblePositionRangeForLine):
+ (WebCore::AccessibilityObject::boundsForVisiblePositionRange):
+ (WebCore::AccessibilityObject::setSelectedVisiblePositionRange):
+ * accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
+ (isTextMarkerIgnored):
+ (-[WebAccessibilityObjectWrapper accessibilityObjectForTextMarker:]):
+ (accessibilityObjectForTextMarker):
+ (-[WebAccessibilityObjectWrapper textMarkerRangeFromRange:]):
+ (textMarkerRangeFromRange):
+ (-[WebAccessibilityObjectWrapper startOrEndTextMarkerForRange:isStart:]):
+ (startOrEndTextmarkerForRange):
+ (-[WebAccessibilityObjectWrapper nextTextMarkerForNode:offset:]):
+ (-[WebAccessibilityObjectWrapper previousTextMarkerForNode:offset:]):
+ (-[WebAccessibilityObjectWrapper textMarkerForNode:offset:]):
+ (textMarkerForCharacterOffset):
+ (-[WebAccessibilityObjectWrapper rangeForTextMarkerRange:]):
+ (-[WebAccessibilityObjectWrapper characterOffsetForTextMarker:]):
+ (textMarkerForVisiblePosition):
+ (-[WebAccessibilityObjectWrapper accessibilityAttributeValue:forParameter:]):
+
2016-01-18 Olivier Blin <olivier.b...@softathome.com>
[Mac] Remove unused playerToPrivateMap()
Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (195239 => 195240)
--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2016-01-19 00:06:46 UTC (rev 195239)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2016-01-19 00:56:13 UTC (rev 195240)
@@ -81,6 +81,7 @@
#include "RenderTableRow.h"
#include "RenderView.h"
#include "ScrollView.h"
+#include "TextIterator.h"
#include <wtf/DataLog.h>
#if ENABLE(VIDEO)
@@ -1418,6 +1419,332 @@
return visiblePos;
}
+CharacterOffset AXObjectCache::traverseToOffsetInRange(RefPtr<Range>range, int offset, bool toNodeEnd, bool stayWithinRange)
+{
+ if (!range)
+ return CharacterOffset();
+
+ int offsetInCharacter = 0;
+ int offsetSoFar = 0;
+ int remaining = 0;
+ int lastLength = 0;
+ Node* currentNode = nullptr;
+ bool finished = false;
+ int lastStartOffset = 0;
+
+ TextIterator iterator(range.get());
+
+ // When the range has zero length, there might be replaced node or brTag that we need to increment the characterOffset.
+ if (iterator.atEnd()) {
+ currentNode = &range->startContainer();
+ lastStartOffset = range->startOffset();
+ if (offset > 0 || toNodeEnd) {
+ if (AccessibilityObject::replacedNodeNeedsCharacter(currentNode) || (currentNode->renderer() && currentNode->renderer()->isBR()))
+ offsetSoFar++;
+ lastLength = offsetSoFar;
+
+ // When going backwards, stayWithinRange is false.
+ // Here when we don't have any character to move and we are going backwards, we traverse to the previous node.
+ if (!lastLength && toNodeEnd && !stayWithinRange) {
+ if (Node* preNode = previousNode(currentNode))
+ return traverseToOffsetInRange(rangeForNodeContents(preNode), offset, toNodeEnd);
+ return CharacterOffset();
+ }
+ }
+ }
+
+ for (; !iterator.atEnd(); iterator.advance()) {
+ int currentLength = iterator.text().length();
+
+ Node& node = iterator.range()->startContainer();
+ currentNode = &node;
+ // When currentLength == 0, we check if there's any replaced node.
+ // If not, we skip the node with no length.
+ if (!currentLength) {
+ int subOffset = iterator.range()->startOffset();
+ Node* childNode = node.traverseToChildAt(subOffset);
+ if (AccessibilityObject::replacedNodeNeedsCharacter(childNode)) {
+ offsetSoFar++;
+ currentLength++;
+ currentNode = childNode;
+ } else
+ continue;
+ } else {
+ // Ignore space, new line, tag node.
+ if (currentLength == 1 && isSpaceOrNewline(iterator.text()[0]))
+ continue;
+ offsetSoFar += currentLength;
+ }
+
+ lastLength = currentLength;
+ lastStartOffset = iterator.range()->startOffset();
+
+ // Break early if we have advanced enough characters.
+ if (!toNodeEnd && offsetSoFar >= offset) {
+ offsetInCharacter = offset - (offsetSoFar - currentLength);
+ finished = true;
+ break;
+ }
+ }
+
+ if (!finished) {
+ offsetInCharacter = lastLength;
+ if (!toNodeEnd)
+ remaining = offset - offsetSoFar;
+ }
+
+ return CharacterOffset(currentNode, lastStartOffset, offsetInCharacter, remaining);
+}
+
+int AXObjectCache::lengthForRange(Range* range)
+{
+ if (!range)
+ return -1;
+
+ int length = 0;
+ for (TextIterator it(range); !it.atEnd(); it.advance()) {
+ // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
+ if (it.text().length())
+ length += it.text().length();
+ else {
+ // locate the node and starting offset for this replaced range
+ Node& node = it.range()->startContainer();
+ int offset = it.range()->startOffset();
+ if (AccessibilityObject::replacedNodeNeedsCharacter(node.traverseToChildAt(offset)))
+ ++length;
+ }
+ }
+
+ return length;
+}
+
+RefPtr<Range> AXObjectCache::rangeForNodeContents(Node* node)
+{
+ if (!node)
+ return nullptr;
+
+ Document* document = &node->document();
+ if (!document)
+ return nullptr;
+ RefPtr<Range> range = Range::create(*document);
+ ExceptionCode ec = 0;
+ range->selectNodeContents(node, ec);
+ return ec ? nullptr : range;
+}
+
+static bool characterOffsetsInOrder(const CharacterOffset& characterOffset1, const CharacterOffset& characterOffset2)
+{
+ if (characterOffset1.isNull() || characterOffset2.isNull())
+ return false;
+
+ if (characterOffset1.node == characterOffset2.node)
+ return characterOffset1.offset <= characterOffset2.offset;
+
+ RefPtr<Range> range1 = AXObjectCache::rangeForNodeContents(characterOffset1.node);
+ RefPtr<Range> range2 = AXObjectCache::rangeForNodeContents(characterOffset2.node);
+ return range1->compareBoundaryPoints(Range::START_TO_START, range2.get(), IGNORE_EXCEPTION) <= 0;
+}
+
+RefPtr<Range> AXObjectCache::rangeForUnorderedCharacterOffsets(const CharacterOffset& characterOffset1, const CharacterOffset& characterOffset2)
+{
+ if (characterOffset1.isNull() || characterOffset2.isNull())
+ return nullptr;
+
+ bool alreadyInOrder = characterOffsetsInOrder(characterOffset1, characterOffset2);
+ CharacterOffset startCharacterOffset = alreadyInOrder ? characterOffset1 : characterOffset2;
+ CharacterOffset endCharacterOffset = alreadyInOrder ? characterOffset2 : characterOffset1;
+
+ int endOffset = endCharacterOffset.offset;
+
+ // endOffset can be out of bounds sometimes if the node is a replaced node or has brTag.
+ if (startCharacterOffset.node == endCharacterOffset.node) {
+ RefPtr<Range> nodeRange = AXObjectCache::rangeForNodeContents(startCharacterOffset.node);
+ int nodeLength = TextIterator::rangeLength(nodeRange.get());
+ if (endOffset > nodeLength)
+ endOffset = nodeLength;
+ }
+
+ int startOffset = startCharacterOffset.startIndex + startCharacterOffset.offset;
+ endOffset = endCharacterOffset.startIndex + endOffset;
+
+ // If start node is a replaced node and it has children, we want to include the replaced node itself in the range.
+ Node* startNode = startCharacterOffset.node;
+ if (AccessibilityObject::replacedNodeNeedsCharacter(startNode) && (startNode->hasChildNodes() || startNode != endCharacterOffset.node)) {
+ startOffset = startNode->computeNodeIndex();
+ startNode = startNode->parentNode();
+ }
+
+ RefPtr<Range> result = Range::create(m_document);
+ ExceptionCode ecStart = 0, ecEnd = 0;
+ result->setStart(startNode, startOffset, ecStart);
+ result->setEnd(endCharacterOffset.node, endOffset, ecEnd);
+ if (ecStart || ecEnd)
+ return nullptr;
+
+ return result;
+}
+
+void AXObjectCache::setTextMarkerDataWithCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset)
+{
+ if (characterOffset.isNull())
+ return;
+
+ Node* domNode = characterOffset.node;
+ if (is<HTMLInputElement>(*domNode) && downcast<HTMLInputElement>(*domNode).isPasswordField()) {
+ textMarkerData.ignored = true;
+ return;
+ }
+
+ RefPtr<AccessibilityObject> obj = this->getOrCreate(domNode);
+
+ // Convert to visible position.
+ VisiblePosition visiblePosition = visiblePositionFromCharacterOffset(obj.get(), characterOffset);
+ int vpOffset = 0;
+ if (!visiblePosition.isNull()) {
+ Position deepPos = visiblePosition.deepEquivalent();
+ vpOffset = deepPos.deprecatedEditingOffset();
+ }
+
+ textMarkerData.axID = obj.get()->axObjectID();
+ textMarkerData.node = domNode;
+ textMarkerData.characterOffset = characterOffset.offset;
+ textMarkerData.characterStartIndex = characterOffset.startIndex;
+ textMarkerData.offset = vpOffset;
+ textMarkerData.affinity = visiblePosition.affinity();
+
+ this->setNodeInUse(domNode);
+}
+
+void AXObjectCache::startOrEndTextMarkerDataForRange(TextMarkerData& textMarkerData, RefPtr<Range> range, bool isStart)
+{
+ memset(&textMarkerData, 0, sizeof(TextMarkerData));
+
+ if (!range)
+ return;
+
+ // If it's end text marker, we want to go to the end of the range, and stay within the range.
+ bool stayWithinRange = !isStart;
+
+ // Change the start of the range, so the character offset starts from node beginning.
+ int offset = 0;
+ Node* node = &range->startContainer();
+ if (node->offsetInCharacters()) {
+ CharacterOffset nodeStartOffset = traverseToOffsetInRange(rangeForNodeContents(node), 0, false);
+ offset = std::max(range->startOffset() - nodeStartOffset.startIndex, 0);
+ range->setStart(node, nodeStartOffset.startIndex);
+ }
+
+ CharacterOffset characterOffset = traverseToOffsetInRange(range, offset, !isStart, stayWithinRange);
+ setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset);
+}
+
+void AXObjectCache::textMarkerDataForCharacterOffset(TextMarkerData& textMarkerData, Node& node, int offset, bool toNodeEnd)
+{
+ memset(&textMarkerData, 0, sizeof(TextMarkerData));
+
+ Node* domNode = &node;
+ if (!domNode)
+ return;
+
+ // If offset <= 0, means we want to go to the previous node.
+ if (offset <= 0 && !toNodeEnd) {
+ // 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;
+ domNode = previousNode(domNode);
+ if (domNode) {
+ textMarkerDataForCharacterOffset(textMarkerData, *domNode, 0, true);
+ offset--;
+ } else
+ return;
+ }
+ if (offset > 0)
+ textMarkerDataForCharacterOffset(textMarkerData, *domNode, offset, false);
+ return;
+ }
+
+ RefPtr<Range> range = rangeForNodeContents(domNode);
+
+ // Traverse the offset amount of characters forward and see if there's remaining offsets.
+ // Keep traversing to the next node when there's remaining offsets.
+ CharacterOffset characterOffset = traverseToOffsetInRange(range, offset, toNodeEnd);
+ while (!characterOffset.isNull() && characterOffset.remaining() && !toNodeEnd) {
+ domNode = nextNode(domNode);
+ if (!domNode)
+ return;
+ range = rangeForNodeContents(domNode);
+ characterOffset = traverseToOffsetInRange(range, characterOffset.remaining(), toNodeEnd);
+ }
+
+ setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset);
+}
+
+Node* AXObjectCache::nextNode(Node* node) const
+{
+ if (!node)
+ return nullptr;
+
+ return NodeTraversal::nextSkippingChildren(*node);
+}
+
+Node* AXObjectCache::previousNode(Node* node) const
+{
+ if (!node)
+ return nullptr;
+
+ // First child of body shouldn't have previous node.
+ if (node->parentNode() && node->parentNode()->renderer() && node->parentNode()->renderer()->isBody() && !node->previousSibling())
+ return nullptr;
+
+ return NodeTraversal::previousSkippingChildren(*node);
+}
+
+VisiblePosition AXObjectCache::visiblePositionFromCharacterOffset(AccessibilityObject* obj, const CharacterOffset& characterOffset)
+{
+ if (!obj)
+ return VisiblePosition();
+
+ // nextVisiblePosition means advancing one character. Use this to calculate the character offset.
+ VisiblePositionRange vpRange = obj->visiblePositionRange();
+ VisiblePosition start = vpRange.start;
+ VisiblePosition result = start;
+ for (int i = 0; i < characterOffset.offset; i++)
+ result = obj->nextVisiblePosition(result);
+
+ return result;
+}
+
+CharacterOffset AXObjectCache::characterOffsetFromVisiblePosition(AccessibilityObject* obj, const VisiblePosition& visiblePos)
+{
+ if (!obj)
+ return 0;
+
+ // 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;
+ Position vpDeepPos = vp.deepEquivalent();
+
+ while (!vpDeepPos.isNull() && !deepPos.equals(vpDeepPos)) {
+ vp = obj->nextVisiblePosition(vp);
+ vpDeepPos = vp.deepEquivalent();
+ characterOffset++;
+ }
+
+ return traverseToOffsetInRange(rangeForNodeContents(obj->node()), characterOffset, false);
+}
+
+AccessibilityObject* AXObjectCache::accessibilityObjectForTextMarkerData(TextMarkerData& textMarkerData)
+{
+ if (!isNodeInUse(textMarkerData.node))
+ return nullptr;
+
+ Node* domNode = textMarkerData.node;
+ return this->getOrCreate(domNode);
+}
+
void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
{
// This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence.
@@ -1443,8 +1770,13 @@
textMarkerData.axID = obj.get()->axObjectID();
textMarkerData.node = domNode;
textMarkerData.offset = deepPos.deprecatedEditingOffset();
- textMarkerData.affinity = visiblePos.affinity();
+ textMarkerData.affinity = visiblePos.affinity();
+ // convert to character offset
+ CharacterOffset characterOffset = characterOffsetFromVisiblePosition(obj.get(), visiblePos);
+ textMarkerData.characterOffset = characterOffset.offset;
+ textMarkerData.characterStartIndex = characterOffset.startIndex;
+
cache->setNodeInUse(domNode);
}
Modified: trunk/Source/WebCore/accessibility/AXObjectCache.h (195239 => 195240)
--- trunk/Source/WebCore/accessibility/AXObjectCache.h 2016-01-19 00:06:46 UTC (rev 195239)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.h 2016-01-19 00:56:13 UTC (rev 195240)
@@ -51,9 +51,29 @@
AXID axID;
Node* node;
int offset;
+ int characterStartIndex;
+ int characterOffset;
+ bool ignored;
EAffinity affinity;
};
+struct CharacterOffset {
+ Node* node;
+ int startIndex;
+ int offset;
+ int remainingOffset;
+
+ CharacterOffset(Node* n = nullptr, int startIndex = 0, int offset = 0, int remaining = 0)
+ : node(n)
+ , startIndex(startIndex)
+ , offset(offset)
+ , remainingOffset(remaining)
+ { }
+
+ int remaining() const { return remainingOffset; }
+ bool isNull() const { return !node; }
+};
+
class AXComputedObjectAttributeCache {
public:
AccessibilityObjectInclusion getIgnored(AXID) const;
@@ -164,6 +184,12 @@
// Text marker utilities.
void textMarkerDataForVisiblePosition(TextMarkerData&, const VisiblePosition&);
VisiblePosition visiblePositionForTextMarkerData(TextMarkerData&);
+ void textMarkerDataForCharacterOffset(TextMarkerData&, Node&, int, bool toNodeEnd = false);
+ 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*);
enum AXNotification {
AXActiveDescendantChanged,
@@ -254,6 +280,13 @@
void setNodeInUse(Node* n) { m_textMarkerNodes.add(n); }
void removeNodeForUse(Node* n) { m_textMarkerNodes.remove(n); }
bool isNodeInUse(Node* n) { return m_textMarkerNodes.contains(n); }
+
+ 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&);
+ void setTextMarkerDataWithCharacterOffset(TextMarkerData&, const CharacterOffset&);
private:
AccessibilityObject* rootWebArea();
Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.cpp (195239 => 195240)
--- trunk/Source/WebCore/accessibility/AccessibilityObject.cpp 2016-01-19 00:06:46 UTC (rev 195239)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.cpp 2016-01-19 00:56:13 UTC (rev 195240)
@@ -715,6 +715,11 @@
return Range::create(*frame->document());
}
+RefPtr<Range> AccessibilityObject::elementRange() const
+{
+ return AXObjectCache::rangeForNodeContents(node());
+}
+
String AccessibilityObject::selectText(AccessibilitySelectTextCriteria* criteria)
{
ASSERT(criteria);
@@ -1203,7 +1208,7 @@
return VisiblePositionRange(startPosition, endPosition);
}
-static bool replacedNodeNeedsCharacter(Node* replacedNode)
+bool AccessibilityObject::replacedNodeNeedsCharacter(Node* replacedNode)
{
// we should always be given a rendered node and a replaced node, but be safe
// replaced nodes are either attachments (widgets) or images
@@ -1228,7 +1233,19 @@
}
return nullptr;
}
+
+static String listMarkerTextForNode(Node* node)
+{
+ RenderListItem* listItem = renderListItemContainerForNode(node);
+ if (!listItem)
+ return String();
+ // If this is in a list item, we need to manually add the text for the list marker
+ // because a RenderListMarker does not have a Node equivalent and thus does not appear
+ // when iterating text.
+ return listItem->markerTextWithSuffix();
+}
+
// Returns the text associated with a list marker if this node is contained within a list item.
String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const
{
@@ -1236,14 +1253,36 @@
if (!isStartOfLine(visiblePositionStart))
return String();
- RenderListItem* listItem = renderListItemContainerForNode(node);
- if (!listItem)
+ return listMarkerTextForNode(node);
+}
+
+String AccessibilityObject::stringForRange(RefPtr<Range> range) const
+{
+ if (!range)
return String();
-
- // If this is in a list item, we need to manually add the text for the list marker
- // because a RenderListMarker does not have a Node equivalent and thus does not appear
- // when iterating text.
- return listItem->markerTextWithSuffix();
+
+ TextIterator it(range.get());
+ if (it.atEnd())
+ return String();
+
+ StringBuilder builder;
+ for (; !it.atEnd(); it.advance()) {
+ // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
+ if (it.text().length()) {
+ // Add a textual representation for list marker text.
+ builder.append(listMarkerTextForNode(it.node()));
+ it.appendTextToStringBuilder(builder);
+ } else {
+ // locate the node and starting offset for this replaced range
+ Node& node = it.range()->startContainer();
+ ASSERT(&node == &it.range()->endContainer());
+ int offset = it.range()->startOffset();
+ if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset)))
+ builder.append(objectReplacementCharacter);
+ }
+ }
+
+ return builder.toString();
}
String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.h (195239 => 195240)
--- trunk/Source/WebCore/accessibility/AccessibilityObject.h 2016-01-19 00:06:46 UTC (rev 195239)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.h 2016-01-19 00:56:13 UTC (rev 195240)
@@ -817,6 +817,9 @@
virtual VisiblePositionRange visiblePositionRange() const { return VisiblePositionRange(); }
virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const { return VisiblePositionRange(); }
+ RefPtr<Range> elementRange() const;
+ static bool replacedNodeNeedsCharacter(Node* replacedNode);
+
VisiblePositionRange visiblePositionRangeForUnorderedPositions(const VisiblePosition&, const VisiblePosition&) const;
VisiblePositionRange positionOfLeftWord(const VisiblePosition&) const;
VisiblePositionRange positionOfRightWord(const VisiblePosition&) const;
@@ -829,6 +832,7 @@
VisiblePositionRange lineRangeForPosition(const VisiblePosition&) const;
String stringForVisiblePositionRange(const VisiblePositionRange&) const;
+ String stringForRange(RefPtr<Range>) const;
virtual IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const { return IntRect(); }
int lengthForVisiblePositionRange(const VisiblePositionRange&) const;
virtual void setSelectedVisiblePositionRange(const VisiblePositionRange&) const { }
Modified: trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm (195239 => 195240)
--- trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm 2016-01-19 00:06:46 UTC (rev 195239)
+++ trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm 2016-01-19 00:56:13 UTC (rev 195240)
@@ -782,6 +782,136 @@
#pragma mark Text Marker helpers
+static bool isTextMarkerIgnored(id textMarker)
+{
+ if (!textMarker)
+ return false;
+
+ TextMarkerData textMarkerData;
+ if (!wkGetBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
+ return false;
+
+ return textMarkerData.ignored;
+}
+
+- (AccessibilityObject*)accessibilityObjectForTextMarker:(id)textMarker
+{
+ return accessibilityObjectForTextMarker(m_object->axObjectCache(), textMarker);
+}
+
+static AccessibilityObject* accessibilityObjectForTextMarker(AXObjectCache* cache, id textMarker)
+{
+ if (!textMarker || !cache || isTextMarkerIgnored(textMarker))
+ return nullptr;
+
+ TextMarkerData textMarkerData;
+ if (!wkGetBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
+ return nullptr;
+ return cache->accessibilityObjectForTextMarkerData(textMarkerData);
+}
+
+- (id)textMarkerRangeFromRange:(const RefPtr<Range>)range
+{
+ return textMarkerRangeFromRange(m_object->axObjectCache(), range);
+}
+
+static id textMarkerRangeFromRange(AXObjectCache *cache, const RefPtr<Range> range)
+{
+ id startTextMarker = startOrEndTextmarkerForRange(cache, range, true);
+ id endTextMarker = startOrEndTextmarkerForRange(cache, range, false);
+ return textMarkerRangeFromMarkers(startTextMarker, endTextMarker);
+}
+
+- (id)startOrEndTextMarkerForRange:(const RefPtr<Range>)range isStart:(BOOL)isStart
+{
+ return startOrEndTextmarkerForRange(m_object->axObjectCache(), range, isStart);
+}
+
+static id startOrEndTextmarkerForRange(AXObjectCache* cache, RefPtr<Range> range, bool isStart)
+{
+ if (!cache)
+ return nil;
+
+ TextMarkerData textMarkerData;
+ cache->startOrEndTextMarkerDataForRange(textMarkerData, range, isStart);
+ if (!textMarkerData.axID)
+ return nil;
+
+ return CFBridgingRelease(wkCreateAXTextMarker(&textMarkerData, sizeof(textMarkerData)));
+}
+
+- (id)nextTextMarkerForNode:(Node&)node offset:(int)offset
+{
+ int nextOffset = offset + 1;
+ id textMarker = [self textMarkerForNode:node offset:nextOffset];
+ if (isTextMarkerIgnored(textMarker))
+ textMarker = [self nextTextMarkerForNode:node offset:nextOffset];
+ return textMarker;
+}
+
+- (id)previousTextMarkerForNode:(Node&)node offset:(int)offset
+{
+ int previousOffset = offset - 1;
+ id textMarker = [self textMarkerForNode:node offset:previousOffset];
+ if (isTextMarkerIgnored(textMarker))
+ textMarker = [self previousTextMarkerForNode:node offset:previousOffset];
+ return textMarker;
+}
+
+- (id)textMarkerForNode:(Node&)node offset:(int)offset
+{
+ return textMarkerForCharacterOffset(m_object->axObjectCache(), node, offset);
+}
+
+static id textMarkerForCharacterOffset(AXObjectCache* cache, Node& node, int offset, bool toNodeEnd = false)
+{
+ if (!cache)
+ return nil;
+
+ Node* domNode = &node;
+ if (!domNode)
+ return nil;
+
+ TextMarkerData textMarkerData;
+ cache->textMarkerDataForCharacterOffset(textMarkerData, node, offset, toNodeEnd);
+ if (!textMarkerData.axID && !textMarkerData.ignored)
+ return nil;
+
+ return CFBridgingRelease(wkCreateAXTextMarker(&textMarkerData, sizeof(textMarkerData)));
+}
+
+- (RefPtr<Range>)rangeForTextMarkerRange:(id)textMarkerRange
+{
+ if (!textMarkerRange)
+ return nullptr;
+
+ id startTextMarker = AXTextMarkerRangeStart(textMarkerRange);
+ id endTextMarker = AXTextMarkerRangeEnd(textMarkerRange);
+
+ if (!startTextMarker || !endTextMarker)
+ return nullptr;
+
+ AXObjectCache* cache = m_object->axObjectCache();
+ if (!cache)
+ return nullptr;
+
+ CharacterOffset startCharacterOffset = [self characterOffsetForTextMarker:startTextMarker];
+ CharacterOffset endCharacterOffset = [self characterOffsetForTextMarker:endTextMarker];
+ return cache->rangeForUnorderedCharacterOffsets(startCharacterOffset, endCharacterOffset);
+}
+
+- (CharacterOffset)characterOffsetForTextMarker:(id)textMarker
+{
+ if (!textMarker || isTextMarkerIgnored(textMarker))
+ return CharacterOffset();
+
+ TextMarkerData textMarkerData;
+ if (!wkGetBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
+ return CharacterOffset();
+
+ return CharacterOffset(textMarkerData.node, textMarkerData.characterStartIndex, textMarkerData.characterOffset);
+}
+
static id textMarkerForVisiblePosition(AXObjectCache* cache, const VisiblePosition& visiblePos)
{
ASSERT(cache);
@@ -3828,16 +3958,15 @@
}
if ([attribute isEqualToString:@"AXUIElementForTextMarker"]) {
- VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
- AccessibilityObject* axObject = m_object->accessibilityObjectForPosition(visiblePos);
+ AccessibilityObject* axObject = [self accessibilityObjectForTextMarker:textMarker];
if (!axObject)
return nil;
return axObject->wrapper();
}
if ([attribute isEqualToString:@"AXTextMarkerRangeForUIElement"]) {
- VisiblePositionRange vpRange = uiElement.get()->visiblePositionRange();
- return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
+ RefPtr<Range> range = uiElement.get()->elementRange();
+ return [self textMarkerRangeFromRange:range];
}
if ([attribute isEqualToString:@"AXLineForTextMarker"]) {
@@ -3851,8 +3980,8 @@
}
if ([attribute isEqualToString:@"AXStringForTextMarkerRange"]) {
- VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange];
- return m_object->stringForVisiblePositionRange(visiblePosRange);
+ RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
+ return m_object->stringForRange(range);
}
if ([attribute isEqualToString:@"AXTextMarkerForPosition"]) {
@@ -3895,20 +4024,23 @@
if (!AXObjectIsTextMarker(textMarker1) || !AXObjectIsTextMarker(textMarker2))
return nil;
- VisiblePosition visiblePos1 = [self visiblePositionForTextMarker:(textMarker1)];
- VisiblePosition visiblePos2 = [self visiblePositionForTextMarker:(textMarker2)];
- VisiblePositionRange vpRange = m_object->visiblePositionRangeForUnorderedPositions(visiblePos1, visiblePos2);
- return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
+ AXObjectCache* cache = m_object->axObjectCache();
+ if (!cache)
+ return nil;
+ CharacterOffset characterOffset1 = [self characterOffsetForTextMarker:textMarker1];
+ CharacterOffset characterOffset2 = [self characterOffsetForTextMarker:textMarker2];
+ RefPtr<Range> range = cache->rangeForUnorderedCharacterOffsets(characterOffset1, characterOffset2);
+ return [self textMarkerRangeFromRange:range];
}
if ([attribute isEqualToString:@"AXNextTextMarkerForTextMarker"]) {
- VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
- return [self textMarkerForVisiblePosition:m_object->nextVisiblePosition(visiblePos)];
+ CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
+ return [self nextTextMarkerForNode:*characterOffset.node offset:characterOffset.offset];
}
if ([attribute isEqualToString:@"AXPreviousTextMarkerForTextMarker"]) {
- VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
- return [self textMarkerForVisiblePosition:m_object->previousVisiblePosition(visiblePos)];
+ CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
+ return [self previousTextMarkerForNode:*characterOffset.node offset:characterOffset.offset];
}
if ([attribute isEqualToString:@"AXLeftWordTextMarkerRangeForTextMarker"]) {
@@ -3994,8 +4126,8 @@
}
if ([attribute isEqualToString:@"AXLengthForTextMarkerRange"]) {
- VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange];
- int length = m_object->lengthForVisiblePositionRange(visiblePosRange);
+ RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
+ int length = AXObjectCache::lengthForRange(range.get());
if (length < 0)
return nil;
return [NSNumber numberWithInt:length];
@@ -4003,13 +4135,13 @@
// Used only by DumpRenderTree (so far).
if ([attribute isEqualToString:@"AXStartTextMarkerForTextMarkerRange"]) {
- VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange];
- return [self textMarkerForVisiblePosition:visiblePosRange.start];
+ RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
+ return [self startOrEndTextMarkerForRange:range isStart:YES];
}
if ([attribute isEqualToString:@"AXEndTextMarkerForTextMarkerRange"]) {
- VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange];
- return [self textMarkerForVisiblePosition:visiblePosRange.end];
+ RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
+ return [self startOrEndTextMarkerForRange:range isStart:NO];
}
#if ENABLE(TREE_DEBUGGING)
Modified: trunk/Tools/ChangeLog (195239 => 195240)
--- trunk/Tools/ChangeLog 2016-01-19 00:06:46 UTC (rev 195239)
+++ trunk/Tools/ChangeLog 2016-01-19 00:56:13 UTC (rev 195240)
@@ -1,3 +1,13 @@
+2016-01-18 Nan Wang <n_w...@apple.com>
+
+ AX: [Mac] Implement next/previous text marker functions using TextIterator
+ https://bugs.webkit.org/show_bug.cgi?id=152728
+
+ Reviewed by Chris Fleizach.
+
+ * WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:
+ (WTR::AccessibilityUIElement::accessibilityElementForTextMarker):
+
2016-01-18 Csaba Osztrogonác <o...@webkit.org>
[cmake] Add testair to the build system
Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm (195239 => 195240)
--- trunk/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm 2016-01-19 00:06:46 UTC (rev 195239)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm 2016-01-19 00:56:13 UTC (rev 195240)
@@ -1801,7 +1801,8 @@
{
BEGIN_AX_OBJC_EXCEPTIONS
id uiElement = [m_element accessibilityAttributeValue:@"AXUIElementForTextMarker" forParameter:(id)marker->platformTextMarker()];
- return AccessibilityUIElement::create(uiElement);
+ if (uiElement)
+ return AccessibilityUIElement::create(uiElement);
END_AX_OBJC_EXCEPTIONS
return nullptr;