Added: trunk/LayoutTests/accessibility/mac/text-marker-word-nav-collapsed-whitespace.html (0 => 203412)
--- trunk/LayoutTests/accessibility/mac/text-marker-word-nav-collapsed-whitespace.html (rev 0)
+++ trunk/LayoutTests/accessibility/mac/text-marker-word-nav-collapsed-whitespace.html 2016-07-19 18:14:02 UTC (rev 203412)
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<meta charset="utf-8">
+<script src=""
+</head>
+<body id="body">
+
+<div id="text">
+Test1
+
+
+
+Test2 Test3
+</div>
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+
+ description("This tests that word navigation is working correctly with collapsed whitespaces.");
+
+ if (window.accessibilityController) {
+
+ var text = accessibilityController.accessibleElementById("text");
+ // Get the actual text node.
+ text = text.childAtIndex(0);
+
+ // Check that we can get the second word "Test2"
+ var textMarkerRange = text.textMarkerRangeForElement(text);
+ var startMarker = text.startTextMarkerForTextMarkerRange(textMarkerRange);
+ var currentMarker = advanceAndVerify(startMarker, 5, text);
+ currentMarker = advanceAndVerify(currentMarker, 1, text);
+
+ // Check that we can get the third word "Test3"
+ currentMarker = advanceAndVerify(currentMarker, 5, text);
+ currentMarker = advanceAndVerify(currentMarker, 1, text);
+
+ function advanceAndVerify(currentMarker, offset, obj) {
+ var previousMarker = currentMarker;
+ for (var i = 0; i < offset; i++) {
+ previousMarker = currentMarker;
+ currentMarker = obj.nextTextMarker(previousMarker);
+ }
+ verifyWordRangeForTextMarker(previousMarker, currentMarker, obj);
+ return currentMarker;
+ }
+
+ function replaceAttachmentInString(str) {
+ var newline = '\n';
+ str = str.replace(String.fromCharCode(65532), "[ATTACHMENT]");
+ str = str.replace(newline, "'line break'");
+ return str;
+ }
+
+ function verifyWordRangeForTextMarker(preMarker, textMarker, obj) {
+ var markerRange = obj.textMarkerRangeForMarkers(preMarker, textMarker);
+ var currentCharacter = obj.stringForTextMarkerRange(markerRange);
+ debug("Current character is: " + currentCharacter);
+
+ var previousWordRange = obj.leftWordTextMarkerRangeForTextMarker(textMarker);
+ var nextWordRange = obj.rightWordTextMarkerRangeForTextMarker(textMarker);
+ var preWord = obj.stringForTextMarkerRange(previousWordRange);
+ var nextWord = obj.stringForTextMarkerRange(nextWordRange);
+ debug("Left word is: " + preWord);
+ debug("Right word is: " + nextWord);
+
+ var preWordStart = obj.previousWordStartTextMarkerForTextMarker(textMarker);
+ var nextWordEnd = obj.nextWordEndTextMarkerForTextMarker(textMarker);
+ var preAndNextWordRange = obj.textMarkerRangeForMarkers(preWordStart, nextWordEnd);
+ var preAndNextWord = obj.stringForTextMarkerRange(preAndNextWordRange);
+ debug("Pre word start to next word end: " + preAndNextWord + "\n");
+ }
+ }
+
+</script>
+
+<script src=""
+</body>
+</html>
\ No newline at end of file
Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (203411 => 203412)
--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2016-07-19 17:27:32 UTC (rev 203411)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2016-07-19 18:14:02 UTC (rev 203412)
@@ -1503,6 +1503,7 @@
return CharacterOffset();
bool toNodeEnd = option & TraverseOptionToNodeEnd;
+ bool validateOffset = option & TraverseOptionValidateOffset;
int offsetInCharacter = 0;
int cumulativeOffset = 0;
@@ -1536,9 +1537,6 @@
// Sometimes text contents in a node are splitted into several iterations, so that iterator.range()->startOffset()
// might not be the correct character count. Here we use a previousNode object to keep track of that.
Node* previousNode = nullptr;
- // When text node has collapsed whitespaces, we need to treat it differently since text iterator
- // will omit the collapsed spaces and make the offset inaccurate.
- Node* collapsedWhitespaceNode = nullptr;
for (; !iterator.atEnd(); iterator.advance()) {
int currentLength = iterator.text().length();
bool hasReplacedNodeOrBR = false;
@@ -1546,10 +1544,6 @@
Node& node = iterator.range()->startContainer();
currentNode = &node;
- // The offset of node with collapsed whitespaces has been calcualted in the first iteration.
- if (currentNode == collapsedWhitespaceNode)
- continue;
-
// When currentLength == 0, we check if there's any replaced node.
// If not, we skip the node with no length.
if (!currentLength) {
@@ -1579,34 +1573,14 @@
continue;
}
}
- } else if (currentNode->isTextNode() && currentNode->renderer()) {
- // When there's collapsed whitespace, the text iterator will only count those spaces as one single space.
- // Here we use the RenderText to get the actual length.
- RenderText* renderedText = downcast<RenderText>(currentNode->renderer());
- int currentStartOffset = iterator.range()->startOffset();
- if (renderedText->style().isCollapsibleWhiteSpace(iterator.text()[currentLength - 1]) && currentLength + currentStartOffset != renderedText->caretMaxOffset()) {
- int appendLength = (&range->endContainer() == currentNode ? range->endOffset() : (int)renderedText->text()->length()) - currentStartOffset;
- lastStartOffset = currentStartOffset;
- cumulativeOffset += appendLength;
- lastLength = appendLength;
-
- // Break early if we have advanced enough characters.
- if (!toNodeEnd && cumulativeOffset >= offset) {
- offsetInCharacter = offset - (cumulativeOffset - lastLength);
- finished = true;
- break;
- }
-
- collapsedWhitespaceNode = currentNode;
- continue;
- }
-
}
cumulativeOffset += currentLength;
}
- if (currentNode == previousNode)
+ if (currentNode == previousNode) {
lastLength += currentLength;
+ lastStartOffset = iterator.range()->endOffset() - lastLength;
+ }
else {
lastLength = currentLength;
lastStartOffset = hasReplacedNodeOrBR ? 0 : iterator.range()->startOffset();
@@ -1613,8 +1587,9 @@
}
// Break early if we have advanced enough characters.
- if (!toNodeEnd && cumulativeOffset >= offset) {
- offsetInCharacter = offset - (cumulativeOffset - lastLength);
+ bool offsetLimitReached = validateOffset ? cumulativeOffset + lastStartOffset >= offset : cumulativeOffset >= offset;
+ if (!toNodeEnd && offsetLimitReached) {
+ offsetInCharacter = validateOffset ? std::max(offset - lastStartOffset, 0) : offset - (cumulativeOffset - lastLength);
finished = true;
break;
}
@@ -1667,7 +1642,11 @@
return nullptr;
RefPtr<Range> range = Range::create(*document);
ExceptionCode ec = 0;
- range->selectNodeContents(*node, ec);
+ // For replaced node without children, we should incluce itself in the range.
+ if (AccessibilityObject::replacedNodeNeedsCharacter(node))
+ range->selectNode(*node, ec);
+ else
+ range->selectNodeContents(*node, ec);
return ec ? nullptr : range;
}
@@ -1811,15 +1790,20 @@
// If it's end text marker, we want to go to the end of the range, and stay within the range.
bool stayWithinRange = !isStart;
+ Node& endNode = range->endContainer();
+ if (endNode.offsetInCharacters() && !isStart)
+ return traverseToOffsetInRange(rangeForNodeContents(&endNode), range->endOffset(), TraverseOptionValidateOffset);
+
Ref<Range> copyRange = *range;
// Change the start of the range, so the character offset starts from node beginning.
int offset = 0;
Node& node = copyRange->startContainer();
if (node.offsetInCharacters()) {
- copyRange = Range::create(range->ownerDocument(), &range->startContainer(), range->startOffset(), &range->endContainer(), range->endOffset());
- CharacterOffset nodeStartOffset = traverseToOffsetInRange(rangeForNodeContents(&node), 0);
- offset = std::max(copyRange->startOffset() - nodeStartOffset.startIndex, 0);
- copyRange->setStart(node, nodeStartOffset.startIndex);
+ CharacterOffset nodeStartOffset = traverseToOffsetInRange(rangeForNodeContents(&node), range->startOffset(), TraverseOptionValidateOffset);
+ if (isStart)
+ return nodeStartOffset;
+ copyRange = Range::create(range->ownerDocument(), &range->startContainer(), 0, &range->endContainer(), range->endOffset());
+ offset += nodeStartOffset.offset;
}
return traverseToOffsetInRange(WTFMove(copyRange), offset, isStart ? TraverseOptionDefault : TraverseOptionToNodeEnd, stayWithinRange);
@@ -1984,7 +1968,7 @@
ASSERT(domNode);
if (domNode->offsetInCharacters())
- return CharacterOffset(domNode, 0, deepPos.deprecatedEditingOffset(), 0);
+ return traverseToOffsetInRange(rangeForNodeContents(domNode), deepPos.deprecatedEditingOffset(), TraverseOptionValidateOffset);
RefPtr<AccessibilityObject> obj = this->getOrCreate(domNode);
if (!obj)
@@ -2186,7 +2170,7 @@
RefPtr<Range> AXObjectCache::rightWordRange(const CharacterOffset& characterOffset)
{
- CharacterOffset start = startCharacterOffsetOfWord(characterOffset);
+ CharacterOffset start = startCharacterOffsetOfWord(characterOffset, RightWordIfOnBoundary);
CharacterOffset end = endCharacterOffsetOfWord(start);
return rangeForUnorderedCharacterOffsets(start, end);
}
@@ -2319,10 +2303,18 @@
return it.atEnd() ? start : characterOffset;
Node& node = it.atEnd() ? searchRange->startContainer() : it.range()->startContainer();
+
+ // SimplifiedBackwardsTextIterator ignores replaced elements.
+ if (AccessibilityObject::replacedNodeNeedsCharacter(characterOffset.node))
+ return characterOffsetForNodeAndOffset(*characterOffset.node, 0);
+ Node* nextSibling = node.nextSibling();
+ if (&node != characterOffset.node && AccessibilityObject::replacedNodeNeedsCharacter(nextSibling))
+ return startOrEndCharacterOffsetForRange(rangeForNodeContents(nextSibling), false);
+
if ((node.isTextNode() && static_cast<int>(next) <= node.maxCharacterOffset()) || (node.renderer() && node.renderer()->isBR() && !next)) {
// The next variable contains a usable index into a text node
- if (&node == characterOffset.node)
- next -= characterOffset.startIndex;
+ if (node.isTextNode())
+ return traverseToOffsetInRange(rangeForNodeContents(&node), next, TraverseOptionValidateOffset);
return characterOffsetForNodeAndOffset(node, next, TraverseOptionIncludeStart);
}
Modified: trunk/Source/WebCore/accessibility/AXObjectCache.h (203411 => 203412)
--- trunk/Source/WebCore/accessibility/AXObjectCache.h 2016-07-19 17:27:32 UTC (rev 203411)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.h 2016-07-19 18:14:02 UTC (rev 203412)
@@ -348,7 +348,7 @@
bool isNodeInUse(Node* n) { return m_textMarkerNodes.contains(n); }
// CharacterOffset functions.
- enum TraverseOption { TraverseOptionDefault = 1 << 0, TraverseOptionToNodeEnd = 1 << 1, TraverseOptionIncludeStart = 1 << 2 };
+ enum TraverseOption { TraverseOptionDefault = 1 << 0, TraverseOptionToNodeEnd = 1 << 1, TraverseOptionIncludeStart = 1 << 2, TraverseOptionValidateOffset = 1 << 3 };
Node* nextNode(Node*) const;
Node* previousNode(Node*) const;
CharacterOffset traverseToOffsetInRange(RefPtr<Range>, int, TraverseOption = TraverseOptionDefault, bool stayWithinRange = false);