Commit ef6b480d authored by David Tseng's avatar David Tseng Committed by Commit Bot

Reland: Fire events on relation sources

Fixes performance regression by only sending event for non *created* tree changes.

 Bug: 808061
Original change's description:
> Add hooks for AXTreeDelegate when a relation target changes
>
>
> Problem
> Suppose a node a has an active descendant b. A page author might change |b| to aria-selected="true" immediately after setting |b| to be the active descendant.
> As a result, we have the events and actions:
> active descendant changed on |a|
> client gets event on |a| and emits output
> attribute changed on |b|
> client gets attribute changed on |b| but focus remains over |a|
>
> Solution
>
> Let the client know about changes in its related nodes.
> For example, changes to |b| would cause an event on |a|.
>
> Bug: 808061
> Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
> Change-Id: I58a3d99d3187db348320223f0bde588015ab6f55
> Reviewed-on: https://chromium-review.googlesource.com/899862
> Commit-Queue: David Tseng <dtseng@chromium.org>
> Reviewed-by: Dominic Mazzoni <dmazzoni@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#534885}

TBR=dmazzoni@chromium.org,dtseng@chromium.org,nektar@chromium.org,aleventhal@chromium.org

Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: Iaf04a51602e091f9bb9767a6bb56e4a5177540d3
Reviewed-on: https://chromium-review.googlesource.com/924769Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Commit-Queue: David Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537636}
parent 35fd82bf
...@@ -1435,3 +1435,35 @@ TEST_F('BackgroundTest', 'TableColumnHeaders', function() { ...@@ -1435,3 +1435,35 @@ TEST_F('BackgroundTest', 'TableColumnHeaders', function() {
.replay(); .replay();
}); });
}); });
TEST_F('BackgroundTest', 'ActiveDescendantUpdates', function() {
var mockFeedback = this.createMockFeedback();
this.runWithLoadedTree(function(root) {/*!
<div aria-label="container" tabindex=0 role="group" id="active"
aria-activedescendant="1">
<div id="1" role="treeitem"></div>
<div id="2" role="treeitem"></div>
<script>
var alt = false;
var active = document.getElementById('active');
var one = document.getElementById('1');
var two = document.getElementById('2');
active.addEventListener('click', function() {
var sel = alt ? one : two;
var unsel = alt ? two : one;
active.setAttribute('aria-activedescendant', sel.id);
sel.setAttribute('aria-selected', true);
unsel.setAttribute('aria-selected', false);
alt = !alt;
});
</script>
*/}, function(root) {
var group = root.firstChild;
mockFeedback.call(group.focus.bind(group))
.call(group.doDefault.bind(group))
.expectSpeech('Tree item', 'Selected', ' 2 of 2 ')
.call(group.doDefault.bind(group))
.expectSpeech('Tree item', 'Selected', ' 1 of 2 ')
.replay();
});
});
...@@ -180,6 +180,11 @@ DesktopAutomationHandler.prototype = { ...@@ -180,6 +180,11 @@ DesktopAutomationHandler.prototype = {
* @param {!AutomationEvent} evt * @param {!AutomationEvent} evt
*/ */
onAriaAttributeChanged: function(evt) { onAriaAttributeChanged: function(evt) {
if (evt.target.activeDescendant) {
this.onActiveDescendantChanged(evt);
return;
}
if (evt.target.state.editable) if (evt.target.state.editable)
return; return;
this.onEventIfInRange(evt); this.onEventIfInRange(evt);
......
...@@ -141,6 +141,8 @@ api::automation::EventType ToAutomationEvent( ...@@ -141,6 +141,8 @@ api::automation::EventType ToAutomationEvent(
return api::automation::EVENT_TYPE_LOADCOMPLETE; return api::automation::EVENT_TYPE_LOADCOMPLETE;
case ui::AXEventGenerator::Event::MENU_ITEM_SELECTED: case ui::AXEventGenerator::Event::MENU_ITEM_SELECTED:
return api::automation::EVENT_TYPE_MENULISTITEMSELECTED; return api::automation::EVENT_TYPE_MENULISTITEMSELECTED;
case ui::AXEventGenerator::Event::RELATED_NODE_CHANGED:
return api::automation::EVENT_TYPE_ARIAATTRIBUTECHANGED;
case ui::AXEventGenerator::Event::ROW_COUNT_CHANGED: case ui::AXEventGenerator::Event::ROW_COUNT_CHANGED:
return api::automation::EVENT_TYPE_ROWCOUNTCHANGED; return api::automation::EVENT_TYPE_ROWCOUNTCHANGED;
case ui::AXEventGenerator::Event::SCROLL_POSITION_CHANGED: case ui::AXEventGenerator::Event::SCROLL_POSITION_CHANGED:
...@@ -150,7 +152,8 @@ api::automation::EventType ToAutomationEvent( ...@@ -150,7 +152,8 @@ api::automation::EventType ToAutomationEvent(
case ui::AXEventGenerator::Event::VALUE_CHANGED: case ui::AXEventGenerator::Event::VALUE_CHANGED:
return api::automation::EVENT_TYPE_VALUECHANGED; return api::automation::EVENT_TYPE_VALUECHANGED;
// These don't have a mapping. // Map these into generic attribute changes (not necessarily aria related,
// but mapping for backward compat).
case ui::AXEventGenerator::Event::COLLAPSED: case ui::AXEventGenerator::Event::COLLAPSED:
case ui::AXEventGenerator::Event::DESCRIPTION_CHANGED: case ui::AXEventGenerator::Event::DESCRIPTION_CHANGED:
case ui::AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED: case ui::AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED:
...@@ -161,7 +164,7 @@ api::automation::EventType ToAutomationEvent( ...@@ -161,7 +164,7 @@ api::automation::EventType ToAutomationEvent(
case ui::AXEventGenerator::Event::ROLE_CHANGED: case ui::AXEventGenerator::Event::ROLE_CHANGED:
case ui::AXEventGenerator::Event::SELECTED_CHANGED: case ui::AXEventGenerator::Event::SELECTED_CHANGED:
case ui::AXEventGenerator::Event::STATE_CHANGED: case ui::AXEventGenerator::Event::STATE_CHANGED:
return api::automation::EVENT_TYPE_NONE; return api::automation::EVENT_TYPE_ARIAATTRIBUTECHANGED;
} }
NOTREACHED(); NOTREACHED();
...@@ -280,6 +283,7 @@ bool AutomationAXTreeWrapper::IsEventTypeHandledByAXEventGenerator( ...@@ -280,6 +283,7 @@ bool AutomationAXTreeWrapper::IsEventTypeHandledByAXEventGenerator(
switch (event_type) { switch (event_type) {
// Generated by AXEventGenerator. // Generated by AXEventGenerator.
case api::automation::EVENT_TYPE_ACTIVEDESCENDANTCHANGED: case api::automation::EVENT_TYPE_ACTIVEDESCENDANTCHANGED:
case api::automation::EVENT_TYPE_ARIAATTRIBUTECHANGED:
case api::automation::EVENT_TYPE_CHECKEDSTATECHANGED: case api::automation::EVENT_TYPE_CHECKEDSTATECHANGED:
case api::automation::EVENT_TYPE_EXPANDEDCHANGED: case api::automation::EVENT_TYPE_EXPANDEDCHANGED:
case api::automation::EVENT_TYPE_INVALIDSTATUSCHANGED: case api::automation::EVENT_TYPE_INVALIDSTATUSCHANGED:
...@@ -322,7 +326,6 @@ bool AutomationAXTreeWrapper::IsEventTypeHandledByAXEventGenerator( ...@@ -322,7 +326,6 @@ bool AutomationAXTreeWrapper::IsEventTypeHandledByAXEventGenerator(
// These events might need to be migrated to AXEventGenerator. // These events might need to be migrated to AXEventGenerator.
case api::automation::EVENT_TYPE_ALERT: case api::automation::EVENT_TYPE_ALERT:
case api::automation::EVENT_TYPE_ARIAATTRIBUTECHANGED:
case api::automation::EVENT_TYPE_BLUR: case api::automation::EVENT_TYPE_BLUR:
case api::automation::EVENT_TYPE_CHILDRENCHANGED: case api::automation::EVENT_TYPE_CHILDRENCHANGED:
case api::automation::EVENT_TYPE_DOCUMENTSELECTIONCHANGED: case api::automation::EVENT_TYPE_DOCUMENTSELECTIONCHANGED:
......
...@@ -208,6 +208,7 @@ void BrowserAccessibilityManagerAndroid::FireGeneratedEvent( ...@@ -208,6 +208,7 @@ void BrowserAccessibilityManagerAndroid::FireGeneratedEvent(
case Event::MENU_ITEM_SELECTED: case Event::MENU_ITEM_SELECTED:
case Event::NAME_CHANGED: case Event::NAME_CHANGED:
case Event::OTHER_ATTRIBUTE_CHANGED: case Event::OTHER_ATTRIBUTE_CHANGED:
case Event::RELATED_NODE_CHANGED:
case Event::ROLE_CHANGED: case Event::ROLE_CHANGED:
case Event::ROW_COUNT_CHANGED: case Event::ROW_COUNT_CHANGED:
case Event::SELECTED_CHANGED: case Event::SELECTED_CHANGED:
......
...@@ -360,6 +360,7 @@ void BrowserAccessibilityManagerMac::FireGeneratedEvent( ...@@ -360,6 +360,7 @@ void BrowserAccessibilityManagerMac::FireGeneratedEvent(
case Event::LIVE_REGION_NODE_CHANGED: case Event::LIVE_REGION_NODE_CHANGED:
case Event::NAME_CHANGED: case Event::NAME_CHANGED:
case Event::OTHER_ATTRIBUTE_CHANGED: case Event::OTHER_ATTRIBUTE_CHANGED:
case Event::RELATED_NODE_CHANGED:
case Event::ROLE_CHANGED: case Event::ROLE_CHANGED:
case Event::SCROLL_POSITION_CHANGED: case Event::SCROLL_POSITION_CHANGED:
case Event::SELECTED_CHANGED: case Event::SELECTED_CHANGED:
......
...@@ -181,6 +181,7 @@ void BrowserAccessibilityManagerWin::FireGeneratedEvent( ...@@ -181,6 +181,7 @@ void BrowserAccessibilityManagerWin::FireGeneratedEvent(
case Event::MENU_ITEM_SELECTED: case Event::MENU_ITEM_SELECTED:
case Event::NAME_CHANGED: case Event::NAME_CHANGED:
case Event::OTHER_ATTRIBUTE_CHANGED: case Event::OTHER_ATTRIBUTE_CHANGED:
case Event::RELATED_NODE_CHANGED:
case Event::ROLE_CHANGED: case Event::ROLE_CHANGED:
case Event::ROW_COUNT_CHANGED: case Event::ROW_COUNT_CHANGED:
case Event::SELECTED_CHANGED: case Event::SELECTED_CHANGED:
......
...@@ -128,6 +128,9 @@ void AXEventGenerator::OnStateChanged(AXTree* tree, ...@@ -128,6 +128,9 @@ void AXEventGenerator::OnStateChanged(AXTree* tree,
AddEvent(node, Event::STATE_CHANGED); AddEvent(node, Event::STATE_CHANGED);
if (state == ax::mojom::State::kExpanded) { if (state == ax::mojom::State::kExpanded) {
AddEvent(node, new_value ? Event::EXPANDED : Event::COLLAPSED); AddEvent(node, new_value ? Event::EXPANDED : Event::COLLAPSED);
// TODO(accessibility): tree in the midst of updates. Disallow access to
// |node|.
if (node->data().role == ax::mojom::Role::kRow || if (node->data().role == ax::mojom::Role::kRow ||
node->data().role == ax::mojom::Role::kTreeItem) { node->data().role == ax::mojom::Role::kTreeItem) {
ui::AXNode* container = node; ui::AXNode* container = node;
...@@ -158,6 +161,9 @@ void AXEventGenerator::OnStringAttributeChanged(AXTree* tree, ...@@ -158,6 +161,9 @@ void AXEventGenerator::OnStringAttributeChanged(AXTree* tree,
switch (attr) { switch (attr) {
case ax::mojom::StringAttribute::kName: case ax::mojom::StringAttribute::kName:
AddEvent(node, Event::NAME_CHANGED); AddEvent(node, Event::NAME_CHANGED);
// TODO(accessibility): tree in the midst of updates. Disallow
// access to |node|.
if (node->data().HasStringAttribute( if (node->data().HasStringAttribute(
ax::mojom::StringAttribute::kContainerLiveStatus)) { ax::mojom::StringAttribute::kContainerLiveStatus)) {
FireLiveRegionEvents(node); FireLiveRegionEvents(node);
...@@ -173,6 +179,8 @@ void AXEventGenerator::OnStringAttributeChanged(AXTree* tree, ...@@ -173,6 +179,8 @@ void AXEventGenerator::OnStringAttributeChanged(AXTree* tree,
AddEvent(node, Event::INVALID_STATUS_CHANGED); AddEvent(node, Event::INVALID_STATUS_CHANGED);
break; break;
case ax::mojom::StringAttribute::kLiveStatus: case ax::mojom::StringAttribute::kLiveStatus:
// TODO(accessibility): tree in the midst of updates. Disallow access to
// |node|.
if (node->data().role != ax::mojom::Role::kAlert) if (node->data().role != ax::mojom::Role::kAlert)
AddEvent(node, Event::LIVE_REGION_CREATED); AddEvent(node, Event::LIVE_REGION_CREATED);
break; break;
...@@ -328,6 +336,9 @@ void AXEventGenerator::OnAtomicUpdateFinished( ...@@ -328,6 +336,9 @@ void AXEventGenerator::OnAtomicUpdateFinished(
FireLiveRegionEvents(change.node); FireLiveRegionEvents(change.node);
} }
} }
if (change.type != NODE_CREATED && change.type != SUBTREE_CREATED)
FireRelationSourceEvents(tree, change.node);
} }
FireActiveDescendantEvents(); FireActiveDescendantEvents();
...@@ -371,4 +382,32 @@ void AXEventGenerator::FireActiveDescendantEvents() { ...@@ -371,4 +382,32 @@ void AXEventGenerator::FireActiveDescendantEvents() {
active_descendant_changed_.clear(); active_descendant_changed_.clear();
} }
void AXEventGenerator::FireRelationSourceEvents(AXTree* tree,
AXNode* target_node) {
int32_t target_id = target_node->id();
std::set<AXNode*> source_nodes;
auto callback = [&](const auto entry) {
const auto target_to_sources = entry.second;
auto sources_it = target_to_sources.find(target_id);
if (sources_it == target_to_sources.end())
return;
auto sources = sources_it->second;
std::for_each(sources.begin(), sources.end(), [&](int32_t source_id) {
AXNode* source_node = tree->GetFromId(source_id);
if (!source_node || source_nodes.count(source_node) > 0)
return;
source_nodes.insert(source_node);
AddEvent(source_node, Event::RELATED_NODE_CHANGED);
});
};
std::for_each(tree->int_reverse_relations().begin(),
tree->int_reverse_relations().end(), callback);
std::for_each(tree->intlist_reverse_relations().begin(),
tree->intlist_reverse_relations().end(), callback);
}
} // namespace ui } // namespace ui
...@@ -38,6 +38,7 @@ class AX_EXPORT AXEventGenerator : public AXTreeDelegate { ...@@ -38,6 +38,7 @@ class AX_EXPORT AXEventGenerator : public AXTreeDelegate {
MENU_ITEM_SELECTED, MENU_ITEM_SELECTED,
NAME_CHANGED, NAME_CHANGED,
OTHER_ATTRIBUTE_CHANGED, OTHER_ATTRIBUTE_CHANGED,
RELATED_NODE_CHANGED,
ROLE_CHANGED, ROLE_CHANGED,
ROW_COUNT_CHANGED, ROW_COUNT_CHANGED,
SCROLL_POSITION_CHANGED, SCROLL_POSITION_CHANGED,
...@@ -169,6 +170,7 @@ class AX_EXPORT AXEventGenerator : public AXTreeDelegate { ...@@ -169,6 +170,7 @@ class AX_EXPORT AXEventGenerator : public AXTreeDelegate {
private: private:
void FireLiveRegionEvents(AXNode* node); void FireLiveRegionEvents(AXNode* node);
void FireActiveDescendantEvents(); void FireActiveDescendantEvents();
void FireRelationSourceEvents(AXTree* tree, AXNode* target_node);
AXTree* tree_ = nullptr; // Not owned. AXTree* tree_ = nullptr; // Not owned.
std::map<AXNode*, std::set<Event>> tree_events_; std::map<AXNode*, std::set<Event>> tree_events_;
......
...@@ -71,6 +71,9 @@ std::string DumpEvents(AXEventGenerator* generator) { ...@@ -71,6 +71,9 @@ std::string DumpEvents(AXEventGenerator* generator) {
case AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED: case AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED:
event_name = "OTHER_ATTRIBUTE_CHANGED"; event_name = "OTHER_ATTRIBUTE_CHANGED";
break; break;
case AXEventGenerator::Event::RELATED_NODE_CHANGED:
event_name = "RELATED_NODE_CHANGED";
break;
case AXEventGenerator::Event::ROLE_CHANGED: case AXEventGenerator::Event::ROLE_CHANGED:
event_name = "ROLE_CHANGED"; event_name = "ROLE_CHANGED";
break; break;
...@@ -349,7 +352,10 @@ TEST(AXEventGeneratorTest, ActiveDescendantChanged) { ...@@ -349,7 +352,10 @@ TEST(AXEventGeneratorTest, ActiveDescendantChanged) {
update.nodes[0].AddIntAttribute(ax::mojom::IntAttribute::kActivedescendantId, update.nodes[0].AddIntAttribute(ax::mojom::IntAttribute::kActivedescendantId,
3); 3);
EXPECT_TRUE(tree.Unserialize(update)); EXPECT_TRUE(tree.Unserialize(update));
EXPECT_EQ("ACTIVE_DESCENDANT_CHANGED on 1", DumpEvents(&event_generator)); EXPECT_EQ(
"ACTIVE_DESCENDANT_CHANGED on 1, "
"RELATED_NODE_CHANGED on 1",
DumpEvents(&event_generator));
} }
TEST(AXEventGeneratorTest, CreateAlertAndLiveRegion) { TEST(AXEventGeneratorTest, CreateAlertAndLiveRegion) {
...@@ -625,7 +631,8 @@ TEST(AXEventGeneratorTest, OtherAttributeChanged) { ...@@ -625,7 +631,8 @@ TEST(AXEventGeneratorTest, OtherAttributeChanged) {
"OTHER_ATTRIBUTE_CHANGED on 3, " "OTHER_ATTRIBUTE_CHANGED on 3, "
"OTHER_ATTRIBUTE_CHANGED on 4, " "OTHER_ATTRIBUTE_CHANGED on 4, "
"OTHER_ATTRIBUTE_CHANGED on 5, " "OTHER_ATTRIBUTE_CHANGED on 5, "
"OTHER_ATTRIBUTE_CHANGED on 6", "OTHER_ATTRIBUTE_CHANGED on 6, "
"RELATED_NODE_CHANGED on 6",
DumpEvents(&event_generator)); DumpEvents(&event_generator));
} }
...@@ -697,7 +704,8 @@ TEST(AXEventGeneratorTest, MenuItemSelected) { ...@@ -697,7 +704,8 @@ TEST(AXEventGeneratorTest, MenuItemSelected) {
EXPECT_TRUE(tree.Unserialize(update)); EXPECT_TRUE(tree.Unserialize(update));
EXPECT_EQ( EXPECT_EQ(
"ACTIVE_DESCENDANT_CHANGED on 1, " "ACTIVE_DESCENDANT_CHANGED on 1, "
"MENU_ITEM_SELECTED on 3", "MENU_ITEM_SELECTED on 3, "
"RELATED_NODE_CHANGED on 1",
DumpEvents(&event_generator)); DumpEvents(&event_generator));
} }
......
...@@ -464,6 +464,10 @@ bool AXTree::UpdateNode(const AXNodeData& src, ...@@ -464,6 +464,10 @@ bool AXTree::UpdateNode(const AXNodeData& src,
AXNode* node = GetFromId(src.id); AXNode* node = GetFromId(src.id);
if (node) { if (node) {
update_state->pending_nodes.erase(node); update_state->pending_nodes.erase(node);
// TODO(accessibility): CallNodeChangeCallbacks should not pass |node|,
// since the tree and the node data are not yet in a consistent
// state. Possibly only pass id.
if (update_state->new_nodes.find(node) == update_state->new_nodes.end()) if (update_state->new_nodes.find(node) == update_state->new_nodes.end())
CallNodeChangeCallbacks(node, src); CallNodeChangeCallbacks(node, src);
UpdateReverseRelations(node, src); UpdateReverseRelations(node, src);
......
...@@ -158,6 +158,13 @@ class AX_EXPORT AXTreeDelegate { ...@@ -158,6 +158,13 @@ class AX_EXPORT AXTreeDelegate {
// accessibility APIs on a specific platform. // accessibility APIs on a specific platform.
class AX_EXPORT AXTree { class AX_EXPORT AXTree {
public: public:
typedef std::map<ax::mojom::IntAttribute,
std::map<int32_t, std::set<int32_t>>>
IntReverseRelationMap;
typedef std::map<ax::mojom::IntListAttribute,
std::map<int32_t, std::set<int32_t>>>
IntListReverseRelationMap;
AXTree(); AXTree();
explicit AXTree(const AXTreeUpdate& initial_state); explicit AXTree(const AXTreeUpdate& initial_state);
virtual ~AXTree(); virtual ~AXTree();
...@@ -214,6 +221,13 @@ class AX_EXPORT AXTree { ...@@ -214,6 +221,13 @@ class AX_EXPORT AXTree {
std::set<int32_t> GetReverseRelations(ax::mojom::IntListAttribute attr, std::set<int32_t> GetReverseRelations(ax::mojom::IntListAttribute attr,
int32_t dst_id) const; int32_t dst_id) const;
// Map from a relation attribute to a map from a target id to source ids.
const IntReverseRelationMap& int_reverse_relations() {
return int_reverse_relations_;
}
const IntListReverseRelationMap& intlist_reverse_relations() {
return intlist_reverse_relations_;
}
// Return a multi-line indented string representation, for logging. // Return a multi-line indented string representation, for logging.
std::string ToString() const; std::string ToString() const;
...@@ -273,12 +287,10 @@ class AX_EXPORT AXTree { ...@@ -273,12 +287,10 @@ class AX_EXPORT AXTree {
// Map from an int attribute (if IsNodeIdIntAttribute is true) to // Map from an int attribute (if IsNodeIdIntAttribute is true) to
// a reverse mapping from target nodes to source nodes. // a reverse mapping from target nodes to source nodes.
std::map<ax::mojom::IntAttribute, std::map<int32_t, std::set<int32_t>>> IntReverseRelationMap int_reverse_relations_;
int_reverse_relations_;
// Map from an int list attribute (if IsNodeIdIntListAttribute is true) to // Map from an int list attribute (if IsNodeIdIntListAttribute is true) to
// a reverse mapping from target nodes to source nodes. // a reverse mapping from target nodes to source nodes.
std::map<ax::mojom::IntListAttribute, std::map<int32_t, std::set<int32_t>>> IntListReverseRelationMap intlist_reverse_relations_;
intlist_reverse_relations_;
}; };
} // namespace ui } // namespace ui
......
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