Commit fb89c75b authored by Nektarios Paisios's avatar Nektarios Paisios Committed by Commit Bot

Introduced workaround 1 of 2 in the new selection API so that it works correctly on ChromeOS

So that ChromeVox and Select to Speak will continue to work with the new selection code,
anchor and focus should be swapped if focus comes before anchor in the accessibility tree.
This is achieved by introducing a new AXTreeData member, sel_is_backward,
and new selectionStart/End automation APIs.
R=dmazzoni@chromium.org, dtseng@chromium.org

Change-Id: I7e322549760c1f8028d0d7115fe0fce1c428cd1d
Bug: 639340
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1551714
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#649730}
parent ddc75bcb
......@@ -676,10 +676,12 @@ CommandHandler.onCommand = function(command) {
true);
} else {
var root = ChromeVoxState.instance.currentRange.start.node.root;
if (root && root.anchorObject && root.focusObject) {
if (root && root.selectionStartObject && root.selectionEndObject) {
var sel = new cursors.Range(
new cursors.Cursor(root.anchorObject, root.anchorOffset),
new cursors.Cursor(root.focusObject, root.focusOffset));
new cursors.Cursor(
root.selectionStartObject, root.selectionStartOffset),
new cursors.Cursor(
root.selectionEndObject, root.selectionEndOffset));
var o = new Output()
.format('@end_selection')
.withSpeechAndBraille(sel, sel, Output.EventType.NAVIGATE)
......
......@@ -344,18 +344,18 @@ TEST_F('ChromeVoxCursorsTest', 'SingleDocSelection', function() {
new cursors.Cursor(p2.firstChild, 4));
function verifySel() {
if (root.anchorObject == link.firstChild) {
assertEquals(link.firstChild, root.anchorObject);
assertEquals(0, root.anchorOffset);
assertEquals(link.firstChild, root.focusObject);
assertEquals(1, root.focusOffset);
if (root.selectionStartObject == link.firstChild) {
assertEquals(link.firstChild, root.selectionStartObject);
assertEquals(0, root.selectionStartOffset);
assertEquals(link.firstChild, root.selectionEndObject);
assertEquals(1, root.selectionEndOffset);
this.listenOnce(root, 'textSelectionChanged', verifySel);
multiSel.select();
} else if (root.anchorObject == p1.firstChild) {
assertEquals(p1.firstChild, root.anchorObject);
assertEquals(2, root.anchorOffset);
assertEquals(p2.firstChild, root.focusObject);
assertEquals(4, root.focusOffset);
} else if (root.selectionStartObject == p1.firstChild) {
assertEquals(p1.firstChild, root.selectionStartObject);
assertEquals(2, root.selectionStartOffset);
assertEquals(p2.firstChild, root.selectionEndObject);
assertEquals(4, root.selectionEndOffset);
}
}
......@@ -400,14 +400,14 @@ TEST_F('ChromeVoxCursorsTest', 'InlineElementOffset', function() {
*/}, function(root) {
root.addEventListener('textSelectionChanged', this.newCallback(function(evt) {
// Test setup moves initial focus; ensure we don't test that here.
if (testNode != root.anchorObject)
if (testNode != root.selectionStartObject)
return;
// This is a little unexpected though not really incorrect; Ctrl+C works.
assertEquals(testNode, root.anchorObject);
assertEquals(ofSelectionNode, root.focusObject);
assertEquals(4, root.anchorOffset);
assertEquals(1, root.focusOffset);
assertEquals(testNode, root.selectionStartObject);
assertEquals(ofSelectionNode, root.selectionEndObject);
assertEquals(4, root.selectionStartOffset);
assertEquals(1, root.selectionEndOffset);
}));
// This is the link's static text.
......
......@@ -372,10 +372,10 @@ DesktopAutomationHandler.prototype = {
* @param {!AutomationEvent} evt
*/
onDocumentSelectionChanged: function(evt) {
var anchor = evt.target.anchorObject;
var selectionStart = evt.target.selectionStartObject;
// No selection.
if (!anchor)
if (!selectionStart)
return;
// A caller requested this event be ignored.
......@@ -384,10 +384,11 @@ DesktopAutomationHandler.prototype = {
return;
// Editable selection.
if (anchor.state[StateType.EDITABLE]) {
anchor = AutomationUtil.getEditableRoot(anchor) || anchor;
if (selectionStart.state[StateType.EDITABLE]) {
selectionStart =
AutomationUtil.getEditableRoot(selectionStart) || selectionStart;
this.onEditableChanged_(
new CustomAutomationEvent(evt.type, anchor, evt.eventFrom));
new CustomAutomationEvent(evt.type, selectionStart, evt.eventFrom));
}
// Non-editable selections are handled in |Background|.
......@@ -536,14 +537,15 @@ DesktopAutomationHandler.prototype = {
}
// Sync the ChromeVox range to the editable, if a selection exists.
var anchorObject = evt.target.root.anchorObject;
var anchorOffset = evt.target.root.anchorOffset || 0;
var focusObject = evt.target.root.focusObject;
var focusOffset = evt.target.root.focusOffset || 0;
if (anchorObject && focusObject) {
var selectionStartObject = evt.target.root.selectionStartObject;
var selectionStartOffset = evt.target.root.selectionStartOffset || 0;
var selectionEndObject = evt.target.root.selectionEndObject;
var selectionEndOffset = evt.target.root.selectionEndOffset || 0;
if (selectionStartObject && selectionEndObject) {
var selectedRange = new cursors.Range(
new cursors.WrappingCursor(anchorObject, anchorOffset),
new cursors.WrappingCursor(focusObject, focusOffset));
new cursors.WrappingCursor(
selectionStartObject, selectionStartOffset),
new cursors.WrappingCursor(selectionEndObject, selectionEndOffset));
// Sync ChromeVox range with selection.
ChromeVoxState.instance.setCurrentRange(selectedRange);
......
......@@ -265,18 +265,20 @@ function AutomationRichEditableText(node) {
AutomationEditableText.call(this, node);
var root = this.node_.root;
if (!root || !root.anchorObject || !root.focusObject ||
root.anchorOffset === undefined || root.focusOffset === undefined)
if (!root || !root.selectionStartObject || !root.selectionEndObject ||
root.selectionStartOffset === undefined ||
root.selectionEndOffset === undefined)
return;
this.anchorLine_ = new editing.EditableLine(
root.anchorObject, root.anchorOffset, root.anchorObject,
root.anchorOffset);
root.selectionStartObject, root.selectionStartOffset,
root.selectionStartObject, root.selectionStartOffset);
this.focusLine_ = new editing.EditableLine(
root.focusObject, root.focusOffset, root.focusObject, root.focusOffset);
this.line_ = new editing.EditableLine(
root.anchorObject, root.anchorOffset, root.focusObject, root.focusOffset);
root.selectionStartObject, root.selectionStartOffset,
root.selectionEndObject, root.selectionEndOffset);
this.updateIntraLineState_(this.line_);
......@@ -358,13 +360,14 @@ AutomationRichEditableText.prototype = {
/** @override */
onUpdate: function(eventFrom) {
var root = this.node_.root;
if (!root.anchorObject || !root.focusObject ||
root.anchorOffset === undefined || root.focusOffset === undefined)
if (!root.selectionStartObject || !root.selectionEndObject ||
root.selectionStartOffset === undefined ||
root.selectionEndOffset === undefined)
return;
var anchorLine = new editing.EditableLine(
root.anchorObject, root.anchorOffset, root.anchorObject,
root.anchorOffset);
root.selectionStartObject, root.selectionStartOffset,
root.selectionStartObject, root.selectionStartOffset);
var focusLine = new editing.EditableLine(
root.focusObject, root.focusOffset, root.focusObject, root.focusOffset);
......@@ -386,8 +389,8 @@ AutomationRichEditableText.prototype = {
return;
} else {
cur = new editing.EditableLine(
root.anchorObject, root.anchorOffset, root.focusObject,
root.focusOffset, baseLineOnStart);
root.selectionStartObject, root.selectionStartOffset,
root.selectionEndObject, root.selectionEndOffset, baseLineOnStart);
}
var prev = this.line_;
this.line_ = cur;
......
......@@ -184,20 +184,21 @@ SelectToSpeak.prototype = {
*/
requestSpeakSelectedText_: function(focusedNode) {
// If nothing is selected, return early.
if (!focusedNode || !focusedNode.root || !focusedNode.root.anchorObject ||
!focusedNode.root.focusObject) {
if (!focusedNode || !focusedNode.root ||
!focusedNode.root.selectionStartObject ||
!focusedNode.root.selectionEndObject) {
this.onNullSelection_();
return;
}
let anchorObject = focusedNode.root.anchorObject;
let anchorOffset = focusedNode.root.anchorOffset || 0;
let focusObject = focusedNode.root.focusObject;
let focusOffset = focusedNode.root.focusOffset || 0;
let anchorObject = focusedNode.root.selectionStartObject;
let anchorOffset = focusedNode.root.selectionStartOffset || 0;
let focusObject = focusedNode.root.selectionEndObject;
let focusOffset = focusedNode.root.selectionEndOffset || 0;
if (anchorObject === focusObject && anchorOffset == focusOffset) {
this.onNullSelection_();
return;
}
// First calculate the equivalant position for this selection.
// First calculate the equivalent position for this selection.
// Sometimes the automation selection returns a offset into a root
// node rather than a child node, which may be a bug. This allows us to
// work around that bug until it is fixed or redefined.
......
......@@ -572,45 +572,104 @@ void AutomationInternalCustomBindings::AddRoutes() {
"GetAnchorObjectID",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
result.Set(v8::Number::New(
isolate, tree_wrapper->tree()->data().sel_anchor_object_id));
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
result.Set(v8::Number::New(isolate, tree_data.sel_anchor_object_id));
});
RouteTreeIDFunction(
"GetAnchorOffset",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
result.Set(v8::Number::New(
isolate, tree_wrapper->tree()->data().sel_anchor_offset));
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
result.Set(v8::Number::New(isolate, tree_data.sel_anchor_offset));
});
RouteTreeIDFunction(
"GetAnchorAffinity",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
result.Set(CreateV8String(
isolate,
ui::ToString(tree_wrapper->tree()->data().sel_anchor_affinity)));
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
result.Set(CreateV8String(isolate,
ui::ToString(tree_data.sel_anchor_affinity)));
});
RouteTreeIDFunction(
"GetFocusObjectID",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
result.Set(v8::Number::New(
isolate, tree_wrapper->tree()->data().sel_focus_object_id));
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
result.Set(v8::Number::New(isolate, tree_data.sel_focus_object_id));
});
RouteTreeIDFunction(
"GetFocusOffset",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
result.Set(v8::Number::New(
isolate, tree_wrapper->tree()->data().sel_focus_offset));
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
result.Set(v8::Number::New(isolate, tree_data.sel_focus_offset));
});
RouteTreeIDFunction(
"GetFocusAffinity",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
result.Set(CreateV8String(
isolate,
ui::ToString(tree_wrapper->tree()->data().sel_focus_affinity)));
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
result.Set(CreateV8String(isolate,
ui::ToString(tree_data.sel_focus_affinity)));
});
RouteTreeIDFunction(
"GetSelectionStartObjectID",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
int32_t start_object_id = tree_data.sel_is_backward
? tree_data.sel_focus_object_id
: tree_data.sel_anchor_object_id;
result.Set(v8::Number::New(isolate, start_object_id));
});
RouteTreeIDFunction(
"GetSelectionStartOffset",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
int start_offset = tree_data.sel_is_backward
? tree_data.sel_focus_offset
: tree_data.sel_anchor_offset;
result.Set(v8::Number::New(isolate, start_offset));
});
RouteTreeIDFunction(
"GetSelectionStartAffinity",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
ax::mojom::TextAffinity start_affinity =
tree_data.sel_is_backward ? tree_data.sel_focus_affinity
: tree_data.sel_anchor_affinity;
result.Set(CreateV8String(isolate, ui::ToString(start_affinity)));
});
RouteTreeIDFunction(
"GetSelectionEndObjectID",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
int32_t end_object_id = tree_data.sel_is_backward
? tree_data.sel_anchor_object_id
: tree_data.sel_focus_object_id;
result.Set(v8::Number::New(isolate, end_object_id));
});
RouteTreeIDFunction(
"GetSelectionEndOffset",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
int end_offset = tree_data.sel_is_backward ? tree_data.sel_anchor_offset
: tree_data.sel_focus_offset;
result.Set(v8::Number::New(isolate, end_offset));
});
RouteTreeIDFunction(
"GetSelectionEndAffinity",
[](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
AutomationAXTreeWrapper* tree_wrapper) {
const ui::AXTreeData& tree_data = tree_wrapper->tree()->data();
ax::mojom::TextAffinity end_affinity =
tree_data.sel_is_backward ? tree_data.sel_anchor_affinity
: tree_data.sel_focus_affinity;
result.Set(CreateV8String(isolate, ui::ToString(end_affinity)));
});
// Bindings that take a Tree ID and Node ID and return a property of the node.
......
......@@ -77,6 +77,55 @@ var GetFocusOffset = natives.GetFocusOffset;
*/
var GetFocusAffinity = natives.GetFocusAffinity;
/**
* The start of the selection always comes before its end in the accessibility
* tree.
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The ID of the object at the start of the
* selection.
*/
var GetSelectionStartObjectID = natives.GetSelectionStartObjectID;
/**
* The start of the selection always comes before its end in the accessibility
* tree.
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The offset at the start of the selection.
*/
var GetSelectionStartOffset = natives.GetSelectionStartOffset;
/**
* The start of the selection always comes before its end in the accessibility
* tree.
* @param {string} axTreeID The id of the accessibility tree.
* @return {?string} The affinity at the start of the selection.
*/
var GetSelectionStartAffinity = natives.GetSelectionStartAffinity;
/**
* The end of the selection always comes after its start in the accessibility
* tree.
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The ID of the object at the end of the selection.
*/
var GetSelectionEndObjectID = natives.GetSelectionEndObjectID;
/**
* The end of the selection always comes after its start in the accessibility
* tree.
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The offset at the end of the selection.
*/
var GetSelectionEndOffset = natives.GetSelectionEndOffset;
/**
* The end of the selection always comes after its start in the accessibility
* tree.
* @param {string} axTreeID The id of the accessibility tree.
* @return {?string} The affinity at the end of the selection.
*/
var GetSelectionEndAffinity = natives.GetSelectionEndAffinity;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
......@@ -1374,43 +1423,87 @@ AutomationRootNodeImpl.prototype = {
},
get anchorObject() {
var id = GetAnchorObjectID(this.treeID);
const id = GetAnchorObjectID(this.treeID);
if (id && id != -1)
return this.get(id);
else
return undefined;
return undefined;
},
get anchorOffset() {
var id = GetAnchorObjectID(this.treeID);
const id = GetAnchorObjectID(this.treeID);
if (id && id != -1)
return GetAnchorOffset(this.treeID);
return undefined;
},
get anchorAffinity() {
var id = GetAnchorObjectID(this.treeID);
const id = GetAnchorObjectID(this.treeID);
if (id && id != -1)
return GetAnchorAffinity(this.treeID);
return undefined;
},
get focusObject() {
var id = GetFocusObjectID(this.treeID);
const id = GetFocusObjectID(this.treeID);
if (id && id != -1)
return this.get(id);
else
return undefined;
return undefined;
},
get focusOffset() {
var id = GetFocusObjectID(this.treeID);
const id = GetFocusObjectID(this.treeID);
if (id && id != -1)
return GetFocusOffset(this.treeID);
return undefined;
},
get focusAffinity() {
var id = GetFocusObjectID(this.treeID);
const id = GetFocusObjectID(this.treeID);
if (id && id != -1)
return GetFocusAffinity(this.treeID);
return undefined;
},
get selectionStartObject() {
const id = GetSelectionStartObjectID(this.treeID);
if (id && id != -1)
return this.get(id);
return undefined;
},
get selectionStartOffset() {
const id = GetSelectionStartObjectID(this.treeID);
if (id && id != -1)
return GetSelectionStartOffset(this.treeID);
return undefined;
},
get selectionStartAffinity() {
const id = GetSelectionStartObjectID(this.treeID);
if (id && id != -1)
return GetSelectionStartAffinity(this.treeID);
return undefined;
},
get selectionEndObject() {
const id = GetSelectionEndObjectID(this.treeID);
if (id && id != -1)
return this.get(id);
return undefined;
},
get selectionEndOffset() {
const id = GetSelectionEndObjectID(this.treeID);
if (id && id != -1)
return GetSelectionEndOffset(this.treeID);
return undefined;
},
get selectionEndAffinity() {
const id = GetSelectionEndObjectID(this.treeID);
if (id && id != -1)
return GetSelectionEndAffinity(this.treeID);
return undefined;
},
get: function(id) {
......@@ -1614,6 +1707,12 @@ utils.expose(AutomationRootNode, AutomationRootNodeImpl, {
'focusObject',
'focusOffset',
'focusAffinity',
'selectionStartObject',
'selectionStartOffset',
'selectionStartAffinity',
'selectionEndObject',
'selectionEndOffset',
'selectionEndAffinity',
],
});
......
......@@ -224,6 +224,14 @@ void PdfAccessibilityTree::Finish() {
}
void PdfAccessibilityTree::UpdateAXTreeDataFromSelection() {
tree_data_.sel_is_backward = false;
if (selection_start_page_index_ > selection_end_page_index_) {
tree_data_.sel_is_backward = true;
} else if (selection_start_page_index_ == selection_end_page_index_ &&
selection_start_char_index_ > selection_end_char_index_) {
tree_data_.sel_is_backward = true;
}
FindNodeOffset(selection_start_page_index_, selection_start_char_index_,
&tree_data_.sel_anchor_object_id,
&tree_data_.sel_anchor_offset);
......@@ -420,6 +428,7 @@ void PdfAccessibilityTree::AddWordStartsAndEnds(
//
bool PdfAccessibilityTree::GetTreeData(ui::AXTreeData* tree_data) const {
tree_data->sel_is_backward = tree_data_.sel_is_backward;
tree_data->sel_anchor_object_id = tree_data_.sel_anchor_object_id;
tree_data->sel_anchor_offset = tree_data_.sel_anchor_offset;
tree_data->sel_focus_object_id = tree_data_.sel_focus_object_id;
......
......@@ -76,6 +76,7 @@ IPC_STRUCT_TRAITS_BEGIN(content::AXContentTreeData)
IPC_STRUCT_TRAITS_MEMBER(loaded)
IPC_STRUCT_TRAITS_MEMBER(loading_progress)
IPC_STRUCT_TRAITS_MEMBER(focus_id)
IPC_STRUCT_TRAITS_MEMBER(sel_is_backward)
IPC_STRUCT_TRAITS_MEMBER(sel_anchor_object_id)
IPC_STRUCT_TRAITS_MEMBER(sel_anchor_offset)
IPC_STRUCT_TRAITS_MEMBER(sel_anchor_affinity)
......
......@@ -6,6 +6,7 @@
#include <stddef.h>
#include <algorithm>
#include <set>
#include "base/memory/ptr_util.h"
......@@ -377,12 +378,14 @@ bool BlinkAXTreeSource::GetTreeData(AXContentTreeData* tree_data) const {
if (!focus().IsNull())
tree_data->focus_id = focus().AxID();
bool is_selection_backward = false;
WebAXObject anchor_object, focus_object;
int anchor_offset, focus_offset;
ax::mojom::TextAffinity anchor_affinity, focus_affinity;
if (base::FeatureList::IsEnabled(features::kNewAccessibilitySelection)) {
root().Selection(anchor_object, anchor_offset, anchor_affinity,
focus_object, focus_offset, focus_affinity);
root().Selection(is_selection_backward, anchor_object, anchor_offset,
anchor_affinity, focus_object, focus_offset,
focus_affinity);
} else {
root().SelectionDeprecated(anchor_object, anchor_offset, anchor_affinity,
focus_object, focus_offset, focus_affinity);
......@@ -391,6 +394,7 @@ bool BlinkAXTreeSource::GetTreeData(AXContentTreeData* tree_data) const {
focus_offset >= 0) {
int32_t anchor_id = anchor_object.AxID();
int32_t focus_id = focus_object.AxID();
tree_data->sel_is_backward = is_selection_backward;
tree_data->sel_anchor_object_id = anchor_id;
tree_data->sel_anchor_offset = anchor_offset;
tree_data->sel_focus_object_id = focus_id;
......
......@@ -656,6 +656,8 @@ gin::ObjectTemplateBuilder WebAXObjectProxy::GetObjectTemplateBuilder(
.SetProperty("stepValue", &WebAXObjectProxy::StepValue)
.SetProperty("valueDescription", &WebAXObjectProxy::ValueDescription)
.SetProperty("childrenCount", &WebAXObjectProxy::ChildrenCount)
.SetProperty("selectionIsBackward",
&WebAXObjectProxy::SelectionIsBackward)
.SetProperty("selectionAnchorObject",
&WebAXObjectProxy::SelectionAnchorObject)
.SetProperty("selectionAnchorOffset",
......@@ -946,17 +948,35 @@ int WebAXObjectProxy::ChildrenCount() {
return count;
}
bool WebAXObjectProxy::SelectionIsBackward() {
accessibility_object_.UpdateLayoutAndCheckValidity();
bool is_selection_backward = false;
blink::WebAXObject anchorObject;
int anchorOffset = -1;
ax::mojom::TextAffinity anchorAffinity;
blink::WebAXObject focusObject;
int focusOffset = -1;
ax::mojom::TextAffinity focusAffinity;
accessibility_object_.Selection(is_selection_backward, anchorObject,
anchorOffset, anchorAffinity, focusObject,
focusOffset, focusAffinity);
return is_selection_backward;
}
v8::Local<v8::Value> WebAXObjectProxy::SelectionAnchorObject() {
accessibility_object_.UpdateLayoutAndCheckValidity();
bool is_selection_backward = false;
blink::WebAXObject anchorObject;
int anchorOffset = -1;
ax::mojom::TextAffinity anchorAffinity;
blink::WebAXObject focusObject;
int focusOffset = -1;
ax::mojom::TextAffinity focusAffinity;
accessibility_object_.Selection(anchorObject, anchorOffset, anchorAffinity,
focusObject, focusOffset, focusAffinity);
accessibility_object_.Selection(is_selection_backward, anchorObject,
anchorOffset, anchorAffinity, focusObject,
focusOffset, focusAffinity);
if (anchorObject.IsNull())
return v8::Null(blink::MainThreadIsolate());
......@@ -966,14 +986,16 @@ v8::Local<v8::Value> WebAXObjectProxy::SelectionAnchorObject() {
int WebAXObjectProxy::SelectionAnchorOffset() {
accessibility_object_.UpdateLayoutAndCheckValidity();
bool is_selection_backward = false;
blink::WebAXObject anchorObject;
int anchorOffset = -1;
ax::mojom::TextAffinity anchorAffinity;
blink::WebAXObject focusObject;
int focusOffset = -1;
ax::mojom::TextAffinity focusAffinity;
accessibility_object_.Selection(anchorObject, anchorOffset, anchorAffinity,
focusObject, focusOffset, focusAffinity);
accessibility_object_.Selection(is_selection_backward, anchorObject,
anchorOffset, anchorAffinity, focusObject,
focusOffset, focusAffinity);
if (anchorOffset < 0)
return -1;
......@@ -983,14 +1005,16 @@ int WebAXObjectProxy::SelectionAnchorOffset() {
std::string WebAXObjectProxy::SelectionAnchorAffinity() {
accessibility_object_.UpdateLayoutAndCheckValidity();
bool is_selection_backward = false;
blink::WebAXObject anchorObject;
int anchorOffset = -1;
ax::mojom::TextAffinity anchorAffinity;
blink::WebAXObject focusObject;
int focusOffset = -1;
ax::mojom::TextAffinity focusAffinity;
accessibility_object_.Selection(anchorObject, anchorOffset, anchorAffinity,
focusObject, focusOffset, focusAffinity);
accessibility_object_.Selection(is_selection_backward, anchorObject,
anchorOffset, anchorAffinity, focusObject,
focusOffset, focusAffinity);
return anchorAffinity == ax::mojom::TextAffinity::kUpstream ? "upstream"
: "downstream";
}
......@@ -998,14 +1022,16 @@ std::string WebAXObjectProxy::SelectionAnchorAffinity() {
v8::Local<v8::Value> WebAXObjectProxy::SelectionFocusObject() {
accessibility_object_.UpdateLayoutAndCheckValidity();
bool is_selection_backward = false;
blink::WebAXObject anchorObject;
int anchorOffset = -1;
ax::mojom::TextAffinity anchorAffinity;
blink::WebAXObject focusObject;
int focusOffset = -1;
ax::mojom::TextAffinity focusAffinity;
accessibility_object_.Selection(anchorObject, anchorOffset, anchorAffinity,
focusObject, focusOffset, focusAffinity);
accessibility_object_.Selection(is_selection_backward, anchorObject,
anchorOffset, anchorAffinity, focusObject,
focusOffset, focusAffinity);
if (focusObject.IsNull())
return v8::Null(blink::MainThreadIsolate());
......@@ -1015,14 +1041,16 @@ v8::Local<v8::Value> WebAXObjectProxy::SelectionFocusObject() {
int WebAXObjectProxy::SelectionFocusOffset() {
accessibility_object_.UpdateLayoutAndCheckValidity();
bool is_selection_backward = false;
blink::WebAXObject anchorObject;
int anchorOffset = -1;
ax::mojom::TextAffinity anchorAffinity;
blink::WebAXObject focusObject;
int focusOffset = -1;
ax::mojom::TextAffinity focusAffinity;
accessibility_object_.Selection(anchorObject, anchorOffset, anchorAffinity,
focusObject, focusOffset, focusAffinity);
accessibility_object_.Selection(is_selection_backward, anchorObject,
anchorOffset, anchorAffinity, focusObject,
focusOffset, focusAffinity);
if (focusOffset < 0)
return -1;
......@@ -1032,14 +1060,16 @@ int WebAXObjectProxy::SelectionFocusOffset() {
std::string WebAXObjectProxy::SelectionFocusAffinity() {
accessibility_object_.UpdateLayoutAndCheckValidity();
bool is_selection_backward = false;
blink::WebAXObject anchorObject;
int anchorOffset = -1;
ax::mojom::TextAffinity anchorAffinity;
blink::WebAXObject focusObject;
int focusOffset = -1;
ax::mojom::TextAffinity focusAffinity;
accessibility_object_.Selection(anchorObject, anchorOffset, anchorAffinity,
focusObject, focusOffset, focusAffinity);
accessibility_object_.Selection(is_selection_backward, anchorObject,
anchorOffset, anchorAffinity, focusObject,
focusOffset, focusAffinity);
return focusAffinity == ax::mojom::TextAffinity::kUpstream ? "upstream"
: "downstream";
}
......
......@@ -76,6 +76,7 @@ class WebAXObjectProxy : public gin::Wrappable<WebAXObjectProxy> {
// The following selection functions return global information about the
// current selection and can be called on any object in the tree.
bool SelectionIsBackward();
v8::Local<v8::Value> SelectionAnchorObject();
int SelectionAnchorOffset();
std::string SelectionAnchorAffinity();
......
......@@ -1115,6 +1115,7 @@ IPC_STRUCT_TRAITS_BEGIN(ui::AXTreeData)
IPC_STRUCT_TRAITS_MEMBER(loaded)
IPC_STRUCT_TRAITS_MEMBER(loading_progress)
IPC_STRUCT_TRAITS_MEMBER(focus_id)
IPC_STRUCT_TRAITS_MEMBER(sel_is_backward)
IPC_STRUCT_TRAITS_MEMBER(sel_anchor_object_id)
IPC_STRUCT_TRAITS_MEMBER(sel_anchor_offset)
IPC_STRUCT_TRAITS_MEMBER(sel_anchor_affinity)
......
......@@ -79,6 +79,12 @@ class WebAXObject {
return *this;
}
BLINK_EXPORT bool operator==(const WebAXObject& other) const;
BLINK_EXPORT bool operator!=(const WebAXObject& other) const;
BLINK_EXPORT bool operator<(const WebAXObject& other) const;
BLINK_EXPORT bool operator<=(const WebAXObject& other) const;
BLINK_EXPORT bool operator>(const WebAXObject& other) const;
BLINK_EXPORT bool operator>=(const WebAXObject& other) const;
BLINK_EXPORT static WebAXObject FromWebNode(const WebNode&);
BLINK_EXPORT static WebAXObject FromWebDocument(const WebDocument&);
BLINK_EXPORT static WebAXObject FromWebDocumentByID(const WebDocument&, int);
......@@ -224,7 +230,8 @@ class WebAXObject {
WebAXObject& focus_object,
int& focus_offset,
ax::mojom::TextAffinity& focus_affinity) const;
BLINK_EXPORT void Selection(WebAXObject& anchor_object,
BLINK_EXPORT void Selection(bool& is_selection_backward,
WebAXObject& anchor_object,
int& anchor_offset,
ax::mojom::TextAffinity& anchor_affinity,
WebAXObject& focus_object,
......@@ -369,6 +376,9 @@ class WebAXObject {
SkMatrix44& container_transform,
bool* clips_children = nullptr) const;
// Exchanges a WebAXObject with another.
BLINK_EXPORT void Swap(WebAXObject& other);
// Returns a brief description of the object, suitable for debugging. E.g. its
// role and name.
BLINK_EXPORT WebString ToString() const;
......
......@@ -769,12 +769,14 @@ static ax::mojom::TextAffinity ToAXAffinity(TextAffinity affinity) {
}
}
void WebAXObject::Selection(WebAXObject& anchor_object,
void WebAXObject::Selection(bool& is_selection_backward,
WebAXObject& anchor_object,
int& anchor_offset,
ax::mojom::TextAffinity& anchor_affinity,
WebAXObject& focus_object,
int& focus_offset,
ax::mojom::TextAffinity& focus_affinity) const {
is_selection_backward = false;
anchor_object = WebAXObject();
anchor_offset = -1;
anchor_affinity = ax::mojom::TextAffinity::kDownstream;
......@@ -802,6 +804,7 @@ void WebAXObject::Selection(WebAXObject& anchor_object,
const AXPosition extent = ax_selection.Extent();
focus_object = WebAXObject(const_cast<AXObject*>(extent.ContainerObject()));
is_selection_backward = base > extent;
if (base.IsTextPosition()) {
anchor_offset = base.TextOffset();
anchor_affinity = ToAXAffinity(base.Affinity());
......@@ -1542,6 +1545,16 @@ bool WebAXObject::ScrollToGlobalPoint(const WebPoint& point) const {
return private_->RequestScrollToGlobalPointAction(point);
}
void WebAXObject::Swap(WebAXObject& other) {
if (IsDetached() || other.IsDetached())
return;
AXObject* temp = private_.Get();
DCHECK(temp) << "|private_| should not be null.";
this->Assign(other);
other = temp;
}
WebString WebAXObject::ToString() const {
if (IsDetached())
return WebString();
......@@ -1556,6 +1569,42 @@ WebAXObject& WebAXObject::operator=(AXObject* object) {
return *this;
}
bool WebAXObject::operator==(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ == *other.private_;
}
bool WebAXObject::operator!=(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ != *other.private_;
}
bool WebAXObject::operator<(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ < *other.private_;
}
bool WebAXObject::operator<=(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ <= *other.private_;
}
bool WebAXObject::operator>(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ > *other.private_;
}
bool WebAXObject::operator>=(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ >= *other.private_;
}
WebAXObject::operator AXObject*() const {
return private_.Get();
}
......
......@@ -941,6 +941,48 @@ chrome.automation.AutomationNode.prototype.focusOffset;
*/
chrome.automation.AutomationNode.prototype.focusAffinity;
/**
* The node at the start of the selection, if any.
* @type {(!chrome.automation.AutomationNode|undefined)}
* @see https://developer.chrome.com/extensions/automation#type-selectionStartObject
*/
chrome.automation.AutomationNode.prototype.selectionStartObject;
/**
* The offset at the start of the selection, if any.
* @type {(number|undefined)}
* @see https://developer.chrome.com/extensions/automation#type-selectionStartOffset
*/
chrome.automation.AutomationNode.prototype.selectionStartOffset;
/**
* The affinity at the start of the selection, if any.
* @type {(string|undefined)}
* @see https://developer.chrome.com/extensions/automation#type-selectionStartAffinity
*/
chrome.automation.AutomationNode.prototype.selectionStartAffinity;
/**
* The node at the end of the selection, if any.
* @type {(!chrome.automation.AutomationNode|undefined)}
* @see https://developer.chrome.com/extensions/automation#type-selectionEndObject
*/
chrome.automation.AutomationNode.prototype.selectionEndObject;
/**
* The offset at the end of the selection, if any.
* @type {(number|undefined)}
* @see https://developer.chrome.com/extensions/automation#type-selectionEndOffset
*/
chrome.automation.AutomationNode.prototype.selectionEndOffset;
/**
* The affinity at the end of the selection, if any.
* @type {(string|undefined)}
* @see https://developer.chrome.com/extensions/automation#type-selectionEndAffinity
*/
chrome.automation.AutomationNode.prototype.selectionEndAffinity;
/**
* The current value for this range.
* @type {(number|undefined)}
......
......@@ -404,7 +404,8 @@ void AXEventGenerator::OnTreeDataChanged(AXTree* tree,
AddEvent(tree->root(), Event::LOAD_COMPLETE);
}
if (new_tree_data.sel_anchor_object_id !=
if (new_tree_data.sel_is_backward != old_tree_data.sel_is_backward ||
new_tree_data.sel_anchor_object_id !=
old_tree_data.sel_anchor_object_id ||
new_tree_data.sel_anchor_offset != old_tree_data.sel_anchor_offset ||
new_tree_data.sel_anchor_affinity != old_tree_data.sel_anchor_affinity ||
......
......@@ -52,6 +52,8 @@ bool AXTreeCombiner::Combine() {
focused_tree = tree_id_map_[focused_tree_id];
combined_.tree_data.focus_id =
MapId(focused_tree_id, focused_tree->tree_data.focus_id);
combined_.tree_data.sel_is_backward =
MapId(focused_tree_id, focused_tree->tree_data.sel_is_backward);
combined_.tree_data.sel_anchor_object_id =
MapId(focused_tree_id, focused_tree->tree_data.sel_anchor_object_id);
combined_.tree_data.sel_focus_object_id =
......
......@@ -47,6 +47,8 @@ std::string AXTreeData::ToString() const {
result += " focus_id=" + base::NumberToString(focus_id);
if (sel_anchor_object_id != -1) {
result +=
(sel_is_backward ? " sel_is_backward=true" : " sel_is_backward=false");
result +=
" sel_anchor_object_id=" + base::NumberToString(sel_anchor_object_id);
result += " sel_anchor_offset=" + base::NumberToString(sel_anchor_offset);
......
......@@ -60,6 +60,7 @@ struct AX_EXPORT AXTreeData {
// (selection end). If the offset could correspond to a position on two
// different lines, sel_upstream_affinity means the cursor is on the first
// line, otherwise it's on the second line.
bool sel_is_backward = false;
int32_t sel_anchor_object_id = -1;
int32_t sel_anchor_offset = -1;
ax::mojom::TextAffinity sel_anchor_affinity =
......
......@@ -19,6 +19,7 @@ struct AXTreeData {
string title;
string url;
int32 focus_id;
bool sel_is_backward;
int32 sel_anchor_object_id;
int32 sel_anchor_offset;
ax.mojom.TextAffinity sel_anchor_affinity;
......
......@@ -27,6 +27,7 @@ bool StructTraits<ax::mojom::AXTreeDataDataView, ui::AXTreeData>::Read(
if (!data.ReadUrl(&out->url))
return false;
out->focus_id = data.focus_id();
out->sel_is_backward = data.sel_is_backward();
out->sel_anchor_object_id = data.sel_anchor_object_id();
out->sel_anchor_offset = data.sel_anchor_offset();
out->sel_anchor_affinity = data.sel_anchor_affinity();
......
......@@ -35,6 +35,9 @@ struct StructTraits<ax::mojom::AXTreeDataDataView, ui::AXTreeData> {
static const std::string& title(const ui::AXTreeData& p) { return p.title; }
static const std::string& url(const ui::AXTreeData& p) { return p.url; }
static int32_t focus_id(const ui::AXTreeData& p) { return p.focus_id; }
static bool sel_is_backward(const ui::AXTreeData& p) {
return p.sel_is_backward;
}
static int32_t sel_anchor_object_id(const ui::AXTreeData& p) {
return p.sel_anchor_object_id;
}
......
......@@ -26,6 +26,7 @@ TEST(AXTreeDataMojomTraitsTest, TestSerializeAndDeserializeAXTreeData) {
input.title = "7";
input.url = "8";
input.focus_id = 9;
input.sel_is_backward = true; // Set to true only for testing purposes.
input.sel_anchor_object_id = 10;
input.sel_anchor_offset = 11;
input.sel_anchor_affinity = ax::mojom::TextAffinity::kUpstream;
......@@ -45,6 +46,7 @@ TEST(AXTreeDataMojomTraitsTest, TestSerializeAndDeserializeAXTreeData) {
EXPECT_EQ("7", output.title);
EXPECT_EQ("8", output.url);
EXPECT_EQ(9, output.focus_id);
EXPECT_TRUE(output.sel_is_backward);
EXPECT_EQ(10, output.sel_anchor_object_id);
EXPECT_EQ(11, output.sel_anchor_offset);
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, output.sel_anchor_affinity);
......
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