Commit f1dfcce8 authored by dtseng's avatar dtseng Committed by Commit bot

Support child-based node offsets when setting accessible selections

https://docs.google.com/document/d/1IZ7HtM0BZlMgNyYo6AUHGL84k
RTnH4qt3fd-hXqfjvs/edit#

Review-Url: https://codereview.chromium.org/2339093003
Cr-Commit-Position: refs/heads/master@{#419325}
parent 3a408779
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<div role="region">
<span role="presentation" id="span1">this is a<a id="link" href="#1">test</a></span>
<span role="presentation" id="span2">of selection</span>
</div>
<script>
var sel = null;
function updateSelectedRangeFromPage() {
sel = window.getSelection().getRangeAt(0);
}
// The accessibility tree contains a region with 3 children, select each.
test(function()
{
assert_not_equals(window.accessibilityController, undefined, 'This test requires accessibilityController');
var axRegion = accessibilityController.accessibleElementById("link").parentElement();
assert_equals(axRegion.role, "AXRole: AXRegion");
// An insertion point before the first child.
axRegion.setSelection(axRegion, 0, axRegion, 0);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "");
assert_true(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.startOffset, 0);
assert_equals(sel.startContainer.textContent, "this is a");
// A straight-forward selection of the first child.
axRegion.setSelection(axRegion, 0, axRegion, 1);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "this is a");
assert_false(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.endContainer.constructor, Text);
assert_equals(sel.startOffset, 0);
assert_equals(sel.startContainer.textContent, "this is a");
assert_equals(sel.endOffset, 9);
assert_equals(sel.endContainer.textContent, "this is a");
// Another insertion point after the first child, before the second.
axRegion.setSelection(axRegion, 1, axRegion, 1);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "");
assert_true(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.startOffset, 9);
assert_equals(sel.startContainer.textContent, "this is a");
// Select the second child.
axRegion.setSelection(axRegion, 1, axRegion, 2);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "test\n");
assert_false(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.endContainer.constructor, Text);
assert_equals(sel.startOffset, 0);
assert_equals(sel.startContainer.textContent, "test");
assert_equals(sel.startContainer.compareDocumentPosition(sel.endContainer), 4 /* before */);
assert_equals(sel.endOffset, 1);
assert_equals(sel.endContainer.textContent, "\n ");
// Next, another insertion point between second and third child.
axRegion.setSelection(axRegion, 2, axRegion, 2);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "");
assert_true(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.startOffset, 1);
assert_equals(sel.startContainer.textContent, "\n ");
// Select the third child.
axRegion.setSelection(axRegion, 2, axRegion, 3);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "of selection");
assert_false(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.endContainer.constructor, Text);
assert_equals(sel.startOffset, 0);
assert_equals(sel.startContainer.textContent, "of selection");
assert_equals(sel.endOffset, 12);
assert_equals(sel.endContainer.textContent, "of selection");
// Select the first and second children.
axRegion.setSelection(axRegion, 0, axRegion, 2);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "this is atest\n");
assert_false(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.endContainer.constructor, Text);
assert_equals(sel.startOffset, 0);
assert_equals(sel.startContainer.textContent, "this is a");
assert_equals(sel.endOffset, 1);
assert_equals(sel.endContainer.textContent, "\n ");
// Select the second and third children.
axRegion.setSelection(axRegion, 1, axRegion, 3);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "test\n of selection");
assert_false(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.endContainer.constructor, Text);
assert_equals(sel.startOffset, 0);
assert_equals(sel.startContainer.textContent, "test");
assert_equals(sel.endOffset, 12);
assert_equals(sel.endContainer.textContent, "of selection");
// Select everything.
axRegion.setSelection(axRegion, 0, axRegion, 3);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "this is atest\n of selection");
assert_false(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.endContainer.constructor, Text);
assert_equals(sel.startOffset, 0);
assert_equals(sel.startContainer.textContent, "this is a");
assert_equals(sel.endOffset, 12);
assert_equals(sel.endContainer.textContent, "of selection");
// Last, the insertion point after third child.
axRegion.setSelection(axRegion, 3, axRegion, 3);
updateSelectedRangeFromPage();
assert_equals(sel.toString(), "");
assert_true(sel.collapsed);
assert_equals(sel.startContainer.constructor, Text);
assert_equals(sel.startOffset, 12);
assert_equals(sel.startContainer.textContent, "of selection");
}, "Select child node at index.");
</script>
......@@ -1817,21 +1817,43 @@ AXLayoutObject* AXLayoutObject::getUnignoredObjectFromNode(Node& node) const
// Convert from an accessible object and offset to a VisiblePosition.
static VisiblePosition toVisiblePosition(AXObject* obj, int offset)
{
// First walk up until we find an accessible object with an associated node.
AXObject* runner = obj;
Node* node = nullptr;
while (runner && !node) {
node = runner->getNode();
runner = runner->parentObject();
if (!obj->getNode())
return VisiblePosition();
Node* node = obj->getNode();
if (!node->isTextNode()) {
int childCount = obj->children().size();
// Place position immediately before the container node, if there was no children.
if (childCount == 0) {
if (!obj->parentObject())
return VisiblePosition();
return toVisiblePosition(obj->parentObject(), obj->indexInParent());
}
if (!node)
// The offsets are child offsets over the AX tree. Note that we allow
// for the offset to equal the number of children as |Range| does.
if (offset < 0 || offset > childCount)
return VisiblePosition();
// If it's not a text node, no conversion is necessary, just create a VisiblePosition
// with this node and offset.
if (!node->isTextNode())
return createVisiblePosition(Position(node, offset));
// Clamp to between 0 and child count - 1.
int clampedOffset =
static_cast<unsigned>(offset) > (obj->children().size() - 1) ? offset - 1 : offset;
AXObject* childObj = obj->children()[clampedOffset];
Node* childNode = childObj->getNode();
if (!childNode || !childNode->parentNode())
return VisiblePosition();
// The index in parent.
int adjustedOffset = childNode->nodeIndex();
// If we had to clamp the offset above, the client wants to select the
// end of the node.
if (clampedOffset != offset)
adjustedOffset++;
return createVisiblePosition(Position::editingPositionOf(childNode->parentNode(), adjustedOffset));
}
// If it is a text node, we need to call some utility functions that use a TextIterator
// to walk the characters of the node and figure out the position corresponding to the
......@@ -1860,6 +1882,7 @@ void AXLayoutObject::setSelection(const AXRange& selection)
return;
}
// The selection offsets are offsets into the accessible value.
if (anchorObject == focusObject
&& anchorObject->getLayoutObject()->isTextControl()) {
HTMLTextFormControlElement* textControl = toLayoutTextControl(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment