Commit 71b43088 authored by Victor Fei's avatar Victor Fei Committed by Commit Bot

Fire WIN SHOW event only on ignored ancestor

Google drive expand/collapse state is not being announced due to
excessive HIDE/SHOW/REORDER events flooding NVDA, and NVDA end up
discarding most events.

For example, if we remove aria-hidden="true" on an ancestor node, which
would cause that many of its descendants to remove their IGNORED
states which triggering too many EVENT_OBJECT_SHOW and causes NVDA
to drop the events. In reality, we only want EVENT_OBJECT_SHOW to be
generated on the ancestor node where aria-hidden="true" is removed, so
not to flood and confuse NVDA with excessive events.

This change fixes the above issue by firing EVENT_OBJECT_SHOW on the
root should the nodes of the entire subtree remove their IGNORED states.

https://crrev.com/c/2309450 implemented suppressing excessive HIDE
events, of which this CL is based on.
Suppressing excessive REORDER events will be addressed in follow up
patches.

AX-RelNotes: NVDA can now announce Google drive "My Drive" expand/
collapse state.

Bug: 1019420
Change-Id: Icecf5d998ea666db4bf21a56f8b8b0b9a1e4aaf4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2337404
Commit-Queue: Victor Fei <vicfei@microsoft.com>
Reviewed-by: default avatarAaron Leventhal <aleventhal@chromium.org>
Reviewed-by: default avatarAdam Ettenberger <Adam.Ettenberger@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#802054}
parent e1832d78
...@@ -480,6 +480,11 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest, ...@@ -480,6 +480,11 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
RunEventTest(FILE_PATH_LITERAL("add-subtree.html")); RunEventTest(FILE_PATH_LITERAL("add-subtree.html"));
} }
IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
AccessibilityEventsChildrenChangedOnlyOnAncestor) {
RunEventTest(FILE_PATH_LITERAL("children-changed-only-on-ancestor.html"));
}
IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest, IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
AccessibilityEventsCheckedStateChanged) { AccessibilityEventsCheckedStateChanged) {
RunEventTest(FILE_PATH_LITERAL("checked-state-changed.html")); RunEventTest(FILE_PATH_LITERAL("checked-state-changed.html"));
......
EVENT_OBJECT_FOCUS on <li#op1> role=ROLE_SYSTEM_LISTITEM name="Apple" SELECTED,FOCUSED,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=1 EVENT_OBJECT_FOCUS on <li#op1> role=ROLE_SYSTEM_LISTITEM name="Apple" SELECTED,FOCUSED,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=1
EVENT_OBJECT_HIDE on <body> role=ROLE_SYSTEM_GROUPING INVISIBLE EVENT_OBJECT_HIDE on <body> role=ROLE_SYSTEM_GROUPING INVISIBLE
EVENT_OBJECT_SELECTION on <li#op1> role=ROLE_SYSTEM_LISTITEM name="Apple" SELECTED,FOCUSED,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=1
IA2_EVENT_ACTIVE_DESCENDANT_CHANGED on <input> role=ROLE_SYSTEM_COMBOBOX EXPANDED,FOCUSABLE,HASPOPUP IA2_STATE_EDITABLE,IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE,IA2_STATE_SUPPORTS_AUTOCOMPLETION IA2_EVENT_ACTIVE_DESCENDANT_CHANGED on <input> role=ROLE_SYSTEM_COMBOBOX EXPANDED,FOCUSABLE,HASPOPUP IA2_STATE_EDITABLE,IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE,IA2_STATE_SUPPORTS_AUTOCOMPLETION
IA2_EVENT_TEXT_INSERTED on <#document> role=ROLE_SYSTEM_DOCUMENT value~=[doc-url] FOCUSABLE new_text={'<obj><obj>' start=0 end=2} IA2_EVENT_TEXT_INSERTED on <#document> role=ROLE_SYSTEM_DOCUMENT value~=[doc-url] FOCUSABLE new_text={'<obj><obj>' start=0 end=2}
IA2_EVENT_TEXT_REMOVED on <#document> role=ROLE_SYSTEM_DOCUMENT value~=[doc-url] FOCUSABLE old_text={'<obj>' start=0 end=1} IA2_EVENT_TEXT_REMOVED on <#document> role=ROLE_SYSTEM_DOCUMENT value~=[doc-url] FOCUSABLE old_text={'<obj>' start=0 end=1}
\ No newline at end of file
EVENT_OBJECT_HIDE on <div#article> role=ROLE_SYSTEM_DOCUMENT INVISIBLE
EVENT_OBJECT_REORDER on <div#tree> role=ROLE_SYSTEM_OUTLINE IA2_STATE_VERTICAL
EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild1" INVISIBLE,FOCUSABLE level=2
EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild2" INVISIBLE,FOCUSABLE level=2
EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild3" INVISIBLE,FOCUSABLE level=2
=== Start Continuation ===
EVENT_OBJECT_REORDER on <div#tree> role=ROLE_SYSTEM_OUTLINE IA2_STATE_VERTICAL
EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild1" FOCUSABLE level=2 PosInSet=1 SetSize=3
EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild2" FOCUSABLE level=2 PosInSet=2 SetSize=3
EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild3" FOCUSABLE level=2 PosInSet=3 SetSize=3
EVENT_OBJECT_SHOW on <div#article> role=ROLE_SYSTEM_DOCUMENT
<!--
@WIN-DENY:IA2_EVENT_TEXT_INSERTED*
@WIN-DENY:IA2_EVENT_TEXT_REMOVED*
@WIN-DENY:EVENT_OBJECT_STATECHANGE*
@WIN-DENY:EVENT_OBJECT_LOCATIONCHANGE*
-->
<html>
<body>
<!-- Show/Hide events only need to occur on the root of what's shown/hidden,
not for each descendant. -->
<div role="tree" id="tree">
<div role="article" id="article">
<div role="group">
<div role="treeitem" tabindex="0">
<div role="link" id="child1">
<div id="grandchild1">grandchild1</div>
</div>
</div>
<div role="treeitem" tabindex="0">
<div role="link" id="child2">
<div id="grandchild2">grandchild2</div>
</div>
</div>
<div role="treeitem" tabindex="0">
<div role="link" id="child3">
<div id="grandchild3">grandchild3</div>
</div>
</div>
</div>
</div>
</div>
<script>
const go_passes = [
// Hide action.
() => {
document.getElementById("article").setAttribute("aria-hidden", "true");
document.getElementById("article").setAttribute("style", "width: 0px; height: 0px;");
},
// Show action.
() => {
document.getElementById("article").setAttribute("aria-hidden", "false");
document.getElementById("article").setAttribute("style", "width: auto; height: auto;");
}
];
let current_pass = 0;
function go() {
go_passes[current_pass++].call();
return current_pass < go_passes.length;
}
</script>
</body>
</html>
AriaProperties changed on role=link, name=Toggle AriaProperties changed on role=link, name=Toggle
AriaProperties changed on role=list, name=list AriaProperties changed on role=list, name=list
AriaProperties changed on role=listitem, name=list item 1
AriaProperties changed on role=listitem, name=list item 2
AriaProperties changed on role=listitem, name=list item 3
ExpandCollapseExpandCollapseState changed on role=link, name=Toggle ExpandCollapseExpandCollapseState changed on role=link, name=Toggle
...@@ -60,7 +60,27 @@ void RemoveEventsDueToIgnoredChanged( ...@@ -60,7 +60,27 @@ void RemoveEventsDueToIgnoredChanged(
RemoveEvent(node_events, AXEventGenerator::Event::CHILDREN_CHANGED); RemoveEvent(node_events, AXEventGenerator::Event::CHILDREN_CHANGED);
RemoveEvent(node_events, AXEventGenerator::Event::DESCRIPTION_CHANGED); RemoveEvent(node_events, AXEventGenerator::Event::DESCRIPTION_CHANGED);
RemoveEvent(node_events, AXEventGenerator::Event::NAME_CHANGED); RemoveEvent(node_events, AXEventGenerator::Event::NAME_CHANGED);
RemoveEvent(node_events, AXEventGenerator::Event::OBJECT_ATTRIBUTE_CHANGED);
RemoveEvent(node_events, AXEventGenerator::Event::SORT_CHANGED);
RemoveEvent(node_events, AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED); RemoveEvent(node_events, AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED);
RemoveEvent(node_events,
AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED);
}
// Add a particular AXEventGenerator::IgnoredChangedState to
// |ignored_changed_states|.
void AddIgnoredChangedState(
AXEventGenerator::IgnoredChangedStatesBitset& ignored_changed_states,
AXEventGenerator::IgnoredChangedState state) {
ignored_changed_states.set(static_cast<size_t>(state));
}
// Returns true if |ignored_changed_states| contains a particular
// AXEventGenerator::IgnoredChangedState.
bool HasIgnoredChangedState(
AXEventGenerator::IgnoredChangedStatesBitset& ignored_changed_states,
AXEventGenerator::IgnoredChangedState state) {
return ignored_changed_states[static_cast<size_t>(state)];
} }
} // namespace } // namespace
...@@ -696,7 +716,8 @@ bool AXEventGenerator::ShouldFireLoadEvents(AXNode* node) { ...@@ -696,7 +716,8 @@ bool AXEventGenerator::ShouldFireLoadEvents(AXNode* node) {
void AXEventGenerator::TrimEventsDueToAncestorIgnoredChanged( void AXEventGenerator::TrimEventsDueToAncestorIgnoredChanged(
AXNode* node, AXNode* node,
std::map<AXNode*, bool>& ancestor_ignored_changed_map) { std::map<AXNode*, IgnoredChangedStatesBitset>&
ancestor_ignored_changed_map) {
DCHECK(node); DCHECK(node);
// Recursively compute and cache ancestor ignored changed results in // Recursively compute and cache ancestor ignored changed results in
...@@ -708,37 +729,86 @@ void AXEventGenerator::TrimEventsDueToAncestorIgnoredChanged( ...@@ -708,37 +729,86 @@ void AXEventGenerator::TrimEventsDueToAncestorIgnoredChanged(
ancestor_ignored_changed_map); ancestor_ignored_changed_map);
} }
// If an ancestor of |node| changed to ignored state, update the corresponding // If an ancestor of |node| changed to ignored state (hide), append hide state
// entry in the map for |node| based on the ancestor result (i.e. if an // to the corresponding entry in the map for |node|. Similarly, if an ancestor
// ancestor changed to ignored state, set the entry in the map to true for the // of |node| removed its ignored state (show), we append show state to the
// current node). If |node|'s state changed to ignored as well, we want to // corresponding entry in map for |node| as well. If |node| flipped its
// remove its IGNORED_CHANGED event. // ignored state as well, we want to remove various events related to
const auto& map_iter = ancestor_ignored_changed_map.find(node->parent()); // IGNORED_CHANGED event.
const auto& events_iter = tree_events_.find(node); const auto& parent_map_iter =
if (map_iter != ancestor_ignored_changed_map.end() && map_iter->second) { ancestor_ignored_changed_map.find(node->parent());
ancestor_ignored_changed_map.insert(std::make_pair(node, true)); const auto& curr_events_iter = tree_events_.find(node);
if (node->IsIgnored() && events_iter != tree_events_.end()) {
RemoveEvent(&(events_iter->second), Event::IGNORED_CHANGED); // Initialize |ancestor_ignored_changed_map[node]| with an empty bitset,
RemoveEventsDueToIgnoredChanged(&(events_iter->second)); // representing neither |node| nor its ancestor has IGNORED_CHANGED.
IgnoredChangedStatesBitset& ancestor_ignored_changed_states =
ancestor_ignored_changed_map[node];
// If |ancestor_ignored_changed_map| contains an entry for |node|'s
// ancestor's and the ancestor has either show/hide state, we want to populate
// |node|'s show/hide state in the map based on its cached ancestor result.
// An empty entry in |ancestor_ignored_changed_map| for |node| means that
// neither |node| nor its ancestor has IGNORED_CHANGED.
if (parent_map_iter != ancestor_ignored_changed_map.end()) {
// Propagate ancestor's show/hide states to |node|'s entry in the map.
if (HasIgnoredChangedState(parent_map_iter->second,
IgnoredChangedState::kHide)) {
AddIgnoredChangedState(ancestor_ignored_changed_states,
IgnoredChangedState::kHide);
} }
if (HasIgnoredChangedState(parent_map_iter->second,
IgnoredChangedState::kShow)) {
AddIgnoredChangedState(ancestor_ignored_changed_states,
IgnoredChangedState::kShow);
}
// If |node| has IGNORED changed with show/hide state that matches one of
// its ancestors' IGNORED changed show/hide states, we want to remove
// |node|'s IGNORED_CHANGED related events.
if (curr_events_iter != tree_events_.end() &&
HasEvent(curr_events_iter->second, Event::IGNORED_CHANGED)) {
if ((HasIgnoredChangedState(parent_map_iter->second,
IgnoredChangedState::kHide) &&
node->IsIgnored()) ||
(HasIgnoredChangedState(parent_map_iter->second,
IgnoredChangedState::kShow) &&
!node->IsIgnored())) {
RemoveEvent(&(curr_events_iter->second), Event::IGNORED_CHANGED);
RemoveEventsDueToIgnoredChanged(&(curr_events_iter->second));
}
if (node->IsIgnored()) {
AddIgnoredChangedState(ancestor_ignored_changed_states,
IgnoredChangedState::kHide);
} else {
AddIgnoredChangedState(ancestor_ignored_changed_states,
IgnoredChangedState::kShow);
}
}
return; return;
} }
// If ignored changed results are not cached, calculate the corresponding // If ignored changed results for ancestors are not cached, calculate the
// entry for |node| in the map using the ignored states and events of |node|. // corresponding entry for |node| in the map using the ignored states and
if (events_iter != tree_events_.end() && // events of |node|.
HasEvent(events_iter->second, Event::IGNORED_CHANGED) && if (curr_events_iter != tree_events_.end() &&
node->IsIgnored()) { HasEvent(curr_events_iter->second, Event::IGNORED_CHANGED)) {
ancestor_ignored_changed_map.insert(std::make_pair(node, true)); if (node->IsIgnored()) {
AddIgnoredChangedState(ancestor_ignored_changed_states,
IgnoredChangedState::kHide);
} else {
AddIgnoredChangedState(ancestor_ignored_changed_states,
IgnoredChangedState::kShow);
}
return; return;
} }
ancestor_ignored_changed_map.insert(std::make_pair(node, false));
} }
void AXEventGenerator::PostprocessEvents() { void AXEventGenerator::PostprocessEvents() {
std::map<AXNode*, bool> ancestor_ignored_changed_map; std::map<AXNode*, IgnoredChangedStatesBitset> ancestor_ignored_changed_map;
std::set<AXNode*> removed_subtree_created_nodes;
auto iter = tree_events_.begin(); auto iter = tree_events_.begin();
while (iter != tree_events_.end()) { while (iter != tree_events_.end()) {
AXNode* node = iter->first; AXNode* node = iter->first;
...@@ -752,10 +822,10 @@ void AXEventGenerator::PostprocessEvents() { ...@@ -752,10 +822,10 @@ void AXEventGenerator::PostprocessEvents() {
} }
if (HasEvent(node_events, Event::IGNORED_CHANGED)) { if (HasEvent(node_events, Event::IGNORED_CHANGED)) {
// If a node toggled its ignored state from show to hide, we only want to // If a node toggled its ignored state, we only want to fire
// fire IGNORED_CHANGED event on the top most ancestor where this ignored // IGNORED_CHANGED event on the top most ancestor where this ignored state
// state change takes place and suppress all the descendants's // change takes place and suppress all the descendants's IGNORED_CHANGED
// IGNORED_CHANGED events. // events.
TrimEventsDueToAncestorIgnoredChanged(node, ancestor_ignored_changed_map); TrimEventsDueToAncestorIgnoredChanged(node, ancestor_ignored_changed_map);
RemoveEventsDueToIgnoredChanged(&node_events); RemoveEventsDueToIgnoredChanged(&node_events);
} }
...@@ -781,13 +851,18 @@ void AXEventGenerator::PostprocessEvents() { ...@@ -781,13 +851,18 @@ void AXEventGenerator::PostprocessEvents() {
// Don't fire subtree created on this node if any of its ancestors also has // Don't fire subtree created on this node if any of its ancestors also has
// subtree created. // subtree created.
while (parent && HasEvent(node_events, Event::SUBTREE_CREATED) && if (HasEvent(node_events, Event::SUBTREE_CREATED)) {
tree_events_.find(parent) != tree_events_.end()) { while (parent &&
if (HasEvent(tree_events_[parent], Event::SUBTREE_CREATED)) { (tree_events_.find(parent) != tree_events_.end() ||
RemoveEvent(&node_events, Event::SUBTREE_CREATED); base::Contains(removed_subtree_created_nodes, parent))) {
break; if (base::Contains(removed_subtree_created_nodes, parent) ||
HasEvent(tree_events_[parent], Event::SUBTREE_CREATED)) {
RemoveEvent(&node_events, Event::SUBTREE_CREATED);
removed_subtree_created_nodes.insert(node);
break;
}
parent = parent->GetUnignoredParent();
} }
parent = parent->GetUnignoredParent();
} }
// If this was the only event, remove the node entirely from the // If this was the only event, remove the node entirely from the
......
...@@ -101,6 +101,10 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver { ...@@ -101,6 +101,10 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
WIN_IACCESSIBLE_STATE_CHANGED, WIN_IACCESSIBLE_STATE_CHANGED,
}; };
// For distinguishing between show and hide state when a node has
// IGNORED_CHANGED event.
enum class IgnoredChangedState : uint8_t { kShow, kHide, kCount = 2 };
struct EventParams { struct EventParams {
EventParams(Event event, EventParams(Event event,
ax::mojom::EventFrom event_from, ax::mojom::EventFrom event_from,
...@@ -140,6 +144,16 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver { ...@@ -140,6 +144,16 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
std::set<EventParams>::const_iterator set_iter_; std::set<EventParams>::const_iterator set_iter_;
}; };
// For storing ignored changed states for a particular node. We use bitset as
// the underlying data structure to improve memory usage.
// We use the index of AXEventGenerator::IgnoredChangedState enum
// to access the bitset data.
// e.g. AXEventGenerator::IgnoredChangedState::kShow has index 0 in the
// IgnoredChangedState enum. If |IgnoredChangedStatesBitset[0]| is set, it
// means IgnoredChangedState::kShow is present. Similarly, kHide has index 1
// in the enum, and it corresponds to |IgnoredChangedStatesBitset[1]|.
using IgnoredChangedStatesBitset =
std::bitset<static_cast<size_t>(IgnoredChangedState::kCount)>;
using const_iterator = Iterator; using const_iterator = Iterator;
using iterator = Iterator; using iterator = Iterator;
using value_type = TargetedEvent; using value_type = TargetedEvent;
...@@ -240,17 +254,23 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver { ...@@ -240,17 +254,23 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
void FireRelationSourceEvents(AXTree* tree, AXNode* target_node); void FireRelationSourceEvents(AXTree* tree, AXNode* target_node);
bool ShouldFireLoadEvents(AXNode* node); bool ShouldFireLoadEvents(AXNode* node);
// Remove excessive events for a tree update containing node. // Remove excessive events for a tree update containing node.
// We remove certain events on a node when it changes to IGNORED state and one // We remove certain events on a node when it flips its IGNORED state to
// of the node's ancestor has also changed to IGNORED in the same tree update. // either show/hide and one of the node's ancestor has also flipped its
// IGNORED state in the same way (show/hide) in the tree update.
// |ancestor_has_ignored_map| contains if a node's ancestor has changed to // |ancestor_has_ignored_map| contains if a node's ancestor has changed to
// IGNORED state. // IGNORED state.
// Map's key is: an ax node. // Map's key is an AXNode.
// Map's value is: // Map's value is a std::bitset containing IgnoredChangedStates(kShow/kHide).
// - True if an ancestor of node changed to IGNORED state. // - Map's value IgnoredChangedStatesBitset contains kShow if an ancestor
// - False if no ancestor of node changed to IGNORED state. // of node removed its IGNORED state.
// - Map's value IgnoredChangedStatesBitset contains kHide if an ancestor
// of node changed to IGNORED state.
// - When IgnoredChangedStatesBitset is not set, it means neither the
// node nor its ancestor has IGNORED_CHANGED.
void TrimEventsDueToAncestorIgnoredChanged( void TrimEventsDueToAncestorIgnoredChanged(
AXNode* node, AXNode* node,
std::map<AXNode*, bool>& ancestor_has_ignored_map); std::map<AXNode*, IgnoredChangedStatesBitset>&
ancestor_ignored_changed_map);
void PostprocessEvents(); void PostprocessEvents();
static void GetRestrictionStates(ax::mojom::Restriction restriction, static void GetRestrictionStates(ax::mojom::Restriction restriction,
bool* is_enabled, bool* is_enabled,
......
...@@ -1102,8 +1102,7 @@ TEST(AXEventGeneratorTest, SubtreeBecomesUnignored) { ...@@ -1102,8 +1102,7 @@ TEST(AXEventGeneratorTest, SubtreeBecomesUnignored) {
UnorderedElementsAre( UnorderedElementsAre(
HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1), HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1),
HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2), HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2), HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2)));
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3)));
} }
TEST(AXEventGeneratorTest, TwoNodesSwapIgnored) { TEST(AXEventGeneratorTest, TwoNodesSwapIgnored) {
...@@ -1169,6 +1168,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly1) { ...@@ -1169,6 +1168,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly1) {
// 1 (IGN) // 1 (IGN)
// / \ // / \
// 2 3 (IGN) // 2 3 (IGN)
// AFTER // AFTER
// 1 (IGN) // 1 (IGN)
// / \ // / \
...@@ -1211,6 +1211,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly2) { ...@@ -1211,6 +1211,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly2) {
// 2 // 2
// / \ // / \
// 3 4 (IGN) // 3 4 (IGN)
// AFTER // AFTER
// 1 // 1
// | // |
...@@ -1259,6 +1260,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly3) { ...@@ -1259,6 +1260,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly3) {
// 2 ___ // 2 ___
// / \ // / \
// 3 (IGN) 4 // 3 (IGN) 4
// AFTER // AFTER
// 1 (IGN) // 1 (IGN)
// | // |
...@@ -1314,6 +1316,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly4) { ...@@ -1314,6 +1316,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly4) {
// ____ 5 _____ // ____ 5 _____
// / | \ // / | \
// 6 (IGN) 7 (IGN) 8 // 6 (IGN) 7 (IGN) 8
// AFTER // AFTER
// 1 (IGN) // 1 (IGN)
// | // |
...@@ -1396,6 +1399,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly5) { ...@@ -1396,6 +1399,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly5) {
// ____ 5 _____ // ____ 5 _____
// / | \ // / | \
// 6 (IGN) 7 8 // 6 (IGN) 7 8
// AFTER // AFTER
// 1 (IGN) // 1 (IGN)
// | // |
...@@ -1476,6 +1480,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly6) { ...@@ -1476,6 +1480,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly6) {
// ____ 5 _____ // ____ 5 _____
// / | \ // / | \
// 6 (IGN) 7 (IGN) 8 // 6 (IGN) 7 (IGN) 8
// AFTER // AFTER
// 1 // 1
// | // |
...@@ -1489,7 +1494,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly6) { ...@@ -1489,7 +1494,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly6) {
// / | \ // / | \
// 6 7 8 (IGN) // 6 7 8 (IGN)
// IGNORED_CHANGED expected on #1, #6, #7, #8 // IGNORED_CHANGED expected on #1, #8
AXTreeUpdate initial_state; AXTreeUpdate initial_state;
initial_state.root_id = 1; initial_state.root_id = 1;
...@@ -1542,8 +1547,6 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly6) { ...@@ -1542,8 +1547,6 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly6) {
HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 6), HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 6),
HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 7), HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 7),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1), HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 6),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 7),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 8))); HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 8)));
} }
...@@ -1558,6 +1561,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly7) { ...@@ -1558,6 +1561,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly7) {
// __ 4 ___ // __ 4 ___
// / \ // / \
// 5 (IGN) 6 (IGN) // 5 (IGN) 6 (IGN)
// AFTER // AFTER
// 1 // 1
// | // |
...@@ -1569,7 +1573,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly7) { ...@@ -1569,7 +1573,7 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly7) {
// / \ // / \
// 5 (IGN) 6 (IGN) // 5 (IGN) 6 (IGN)
// IGNORED_CHANGED expected on #1, #2, #3 // IGNORED_CHANGED expected on #1, #3
AXTreeUpdate initial_state; AXTreeUpdate initial_state;
initial_state.root_id = 1; initial_state.root_id = 1;
...@@ -1613,10 +1617,91 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly7) { ...@@ -1613,10 +1617,91 @@ TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly7) {
UnorderedElementsAre( UnorderedElementsAre(
HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 1), HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 1),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1), HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3))); HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3)));
} }
TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly8) {
// BEFORE
// ____ 1 ____
// | |
// 2 (IGN) 7
// |
// 3 (IGN)
// |
// 4 (IGN)
// |
// 5 (IGN)
// |
// 6 (IGN)
// AFTER
// ____ 1 ____
// | |
// 2 7 (IGN)
// |
// 3
// |
// 4
// |
// 5
// |
// 6
// IGNORED_CHANGED expected on #2, #7
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(7);
initial_state.nodes[0].id = 1;
initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
initial_state.nodes[0].child_ids = {2, 7};
initial_state.nodes[1].id = 2;
initial_state.nodes[1].role = ax::mojom::Role::kGroup;
initial_state.nodes[1].child_ids = {3};
initial_state.nodes[1].AddState(ax::mojom::State::kIgnored);
initial_state.nodes[2].id = 3;
initial_state.nodes[2].role = ax::mojom::Role::kGroup;
initial_state.nodes[2].child_ids = {4};
initial_state.nodes[2].AddState(ax::mojom::State::kIgnored);
initial_state.nodes[3].id = 4;
initial_state.nodes[3].role = ax::mojom::Role::kGroup;
initial_state.nodes[3].child_ids = {5};
initial_state.nodes[3].AddState(ax::mojom::State::kIgnored);
initial_state.nodes[4].id = 5;
initial_state.nodes[4].role = ax::mojom::Role::kGroup;
initial_state.nodes[4].child_ids = {6};
initial_state.nodes[4].AddState(ax::mojom::State::kIgnored);
initial_state.nodes[5].id = 5;
initial_state.nodes[5].role = ax::mojom::Role::kStaticText;
initial_state.nodes[5].AddState(ax::mojom::State::kIgnored);
initial_state.nodes[6].id = 7;
initial_state.nodes[6].role = ax::mojom::Role::kStaticText;
AXTree tree(initial_state);
AXEventGenerator event_generator(&tree);
AXTreeUpdate update = initial_state;
update.nodes[1].RemoveState(ax::mojom::State::kIgnored);
update.nodes[2].RemoveState(ax::mojom::State::kIgnored);
update.nodes[3].RemoveState(ax::mojom::State::kIgnored);
update.nodes[4].RemoveState(ax::mojom::State::kIgnored);
update.nodes[5].RemoveState(ax::mojom::State::kIgnored);
update.nodes[6].AddState(ax::mojom::State::kIgnored);
ASSERT_TRUE(tree.Unserialize(update));
EXPECT_THAT(event_generator,
UnorderedElementsAre(
HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1),
HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2),
HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 7)));
}
TEST(AXEventGeneratorTest, ActiveDescendantChangeOnDescendant) { TEST(AXEventGeneratorTest, ActiveDescendantChangeOnDescendant) {
AXTreeUpdate initial_state; AXTreeUpdate initial_state;
initial_state.root_id = 1; initial_state.root_id = 1;
......
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