summaryrefslogtreecommitdiff
path: root/chromium/ui/accessibility/ax_tree.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ui/accessibility/ax_tree.cc')
-rw-r--r--chromium/ui/accessibility/ax_tree.cc1146
1 files changed, 921 insertions, 225 deletions
diff --git a/chromium/ui/accessibility/ax_tree.cc b/chromium/ui/accessibility/ax_tree.cc
index d464561d01e..9c26f68ce8f 100644
--- a/chromium/ui/accessibility/ax_tree.cc
+++ b/chromium/ui/accessibility/ax_tree.cc
@@ -12,6 +12,7 @@
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/logging.h"
+#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "ui/accessibility/accessibility_switches.h"
@@ -28,6 +29,9 @@ namespace ui {
namespace {
std::string TreeToStringHelper(const AXNode* node, int indent) {
+ if (!node)
+ return "";
+
return std::accumulate(
node->children().cbegin(), node->children().cend(),
std::string(2 * indent, ' ') + node->data().ToString() + "\n",
@@ -101,27 +105,158 @@ bool IsCollapsed(const AXNode* node) {
} // namespace
+// This object is used to track structure changes that will occur for a specific
+// AXID. This includes how many times we expect that a node with a specific AXID
+// will be created and/or destroyed, and how many times a subtree rooted at AXID
+// expects to be destroyed during an AXTreeUpdate.
+//
+// An AXTreeUpdate is a serialized representation of an atomic change to an
+// AXTree. See also |AXTreeUpdate| which documents the nature and invariants
+// required to atomically update the AXTree.
+//
+// The reason that we must track these counts, and the reason these are counts
+// rather than a bool/flag is because an AXTreeUpdate may contain multiple
+// AXNodeData updates for a given AXID. A common way that this occurs is when
+// multiple AXTreeUpdates are merged together, combining their AXNodeData list.
+// Additionally AXIDs may be reused after being removed from the tree,
+// most notably when "reparenting" a node. A "reparent" occurs when an AXID is
+// first destroyed from the tree then created again in the same AXTreeUpdate,
+// which may also occur multiple times with merged updates.
+//
+// We need to accumulate these counts for 3 reasons :
+// 1. To determine what structure changes *will* occur before applying
+// updates to the tree so that we can notify observers of structure changes
+// when the tree is still in a stable and unchanged state.
+// 2. Capture any errors *before* applying updates to the tree structure
+// due to the order of (or lack of) AXNodeData entries in the update
+// so we can abort a bad update instead of applying it partway.
+// 3. To validate that the expectations we accumulate actually match
+// updates that are applied to the tree.
+//
+// To reiterate the invariants that this structure is taking a dependency on
+// from |AXTreeUpdate|, suppose that the next AXNodeData to be applied is
+// |node|. The following invariants must hold:
+// 1. Either
+// a) |node.id| is already in the tree, or
+// b) the tree is empty, and
+// |node| is the new root of the tree, and
+// |node.role| == WebAXRoleRootWebArea.
+// 2. Every child id in |node.child_ids| must either be already a child
+// of this node, or a new id not previously in the tree. It is not
+// allowed to "reparent" a child to this node without first removing
+// that child from its previous parent.
+// 3. When a new id appears in |node.child_ids|, the tree should create a
+// new uninitialized placeholder node for it immediately. That
+// placeholder must be updated within the same AXTreeUpdate, otherwise
+// it's a fatal error. This guarantees the tree is always complete
+// before or after an AXTreeUpdate.
+struct PendingStructureChanges {
+ PendingStructureChanges(const AXNode* node)
+ : destroy_subtree_count(0),
+ destroy_node_count(0),
+ create_node_count(0),
+ node_exists(!!node),
+ parent_node_id((node && node->parent())
+ ? base::Optional<AXNode::AXID>{node->parent()->id()}
+ : base::nullopt),
+ last_known_data(node ? &node->data() : nullptr) {}
+
+ // Returns true if this node has any changes remaining.
+ // This includes pending subtree or node destruction, and node creation.
+ bool DoesNodeExpectAnyStructureChanges() const {
+ return DoesNodeExpectSubtreeWillBeDestroyed() ||
+ DoesNodeExpectNodeWillBeDestroyed() ||
+ DoesNodeExpectNodeWillBeCreated();
+ }
+
+ // Returns true if there are any pending changes that require destroying
+ // this node or its subtree.
+ bool DoesNodeExpectSubtreeOrNodeWillBeDestroyed() const {
+ return DoesNodeExpectSubtreeWillBeDestroyed() ||
+ DoesNodeExpectNodeWillBeDestroyed();
+ }
+
+ // Returns true if the subtree rooted at this node needs to be destroyed
+ // during the update, but this may not be the next action that needs to be
+ // performed on the node.
+ bool DoesNodeExpectSubtreeWillBeDestroyed() const {
+ return destroy_subtree_count;
+ }
+
+ // Returns true if this node needs to be destroyed during the update, but this
+ // may not be the next action that needs to be performed on the node.
+ bool DoesNodeExpectNodeWillBeDestroyed() const { return destroy_node_count; }
+
+ // Returns true if this node needs be created during the update, but this
+ // may not be the next action that needs to be performed on the node.
+ bool DoesNodeExpectNodeWillBeCreated() const { return create_node_count; }
+
+ // Returns true if this node would exist in the tree as of the last pending
+ // update that was processed, and the node has not been provided node data.
+ bool DoesNodeRequireInit() const { return node_exists && !last_known_data; }
+
+ // Keep track of the number of times the subtree rooted at this node
+ // will be destroyed.
+ // An example of when this count may be larger than 1 is if updates were
+ // merged together. A subtree may be [created,] destroyed, created, and
+ // destroyed again within the same |AXTreeUpdate|. The important takeaway here
+ // is that an update may request destruction of a subtree rooted at an
+ // AXID more than once, not that a specific subtree is being destroyed
+ // more than once.
+ int32_t destroy_subtree_count;
+
+ // Keep track of the number of times this node will be destroyed.
+ // An example of when this count may be larger than 1 is if updates were
+ // merged together. A node may be [created,] destroyed, created, and destroyed
+ // again within the same |AXTreeUpdate|. The important takeaway here is that
+ // an AXID may request destruction more than once, not that a specific node
+ // is being destroyed more than once.
+ int32_t destroy_node_count;
+
+ // Keep track of the number of times this node will be created.
+ // An example of when this count may be larger than 1 is if updates were
+ // merged together. A node may be [destroyed,] created, destroyed, and created
+ // again within the same |AXTreeUpdate|. The important takeaway here is that
+ // an AXID may request creation more than once, not that a specific node is
+ // being created more than once.
+ int32_t create_node_count;
+
+ // Keep track of whether this node exists in the tree as of the last pending
+ // update that was processed.
+ bool node_exists;
+
+ // Keep track of the parent id for this node as of the last pending
+ // update that was processed.
+ base::Optional<AXNode::AXID> parent_node_id;
+
+ // Keep track of the last known node data for this node.
+ // This will be null either when a node does not exist in the tree, or
+ // when the node is new and has not been initialized with node data yet.
+ // This is needed to determine what children have changed between pending
+ // updates.
+ const AXNodeData* last_known_data;
+};
+
// Intermediate state to keep track of during a tree update.
struct AXTreeUpdateState {
- AXTreeUpdateState() : new_root(nullptr) {}
- // Returns whether this update changes |node|.
- bool IsChangedNode(const AXNode* node) {
- return changed_node_ids.find(node->id()) != changed_node_ids.end();
- }
+ AXTreeUpdateState(const AXTree& tree)
+ : computing_pending_changes(false),
+ root_will_be_created(false),
+ tree(tree) {}
// Returns whether this update removes |node|.
bool IsRemovedNode(const AXNode* node) const {
- return removed_node_ids.find(node->id()) != removed_node_ids.end();
+ return base::Contains(removed_node_ids, node->id());
}
// Returns whether this update creates |node|.
- bool IsNewNode(const AXNode* node) {
- return new_nodes.find(node) != new_nodes.end();
+ bool IsCreatedNode(const AXNode* node) const {
+ return base::Contains(new_node_ids, node->id());
}
// If this node is removed, it should be considered reparented.
bool IsPotentiallyReparentedNode(const AXNode* node) const {
- return base::Contains(potentially_reparented_ids, node->id());
+ return base::Contains(node_ids_found_in_update, node->id());
}
// Returns whether this update reparents |node|.
@@ -129,49 +264,281 @@ struct AXTreeUpdateState {
return IsPotentiallyReparentedNode(node) && IsRemovedNode(node);
}
+ // Returns true if the node should exist in the tree but doesn't have
+ // any node data yet.
+ bool DoesPendingNodeRequireInit(AXNode::AXID node_id) const {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ PendingStructureChanges* data = GetPendingStructureChanges(node_id);
+ return data && data->DoesNodeRequireInit();
+ }
+
+ // Returns the parent node id for the pending node.
+ base::Optional<AXNode::AXID> GetParentIdForPendingNode(AXNode::AXID node_id) {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ PendingStructureChanges* data = GetOrCreatePendingStructureChanges(node_id);
+ DCHECK(!data->parent_node_id ||
+ ShouldPendingNodeExistInTree(*data->parent_node_id));
+ return data->parent_node_id;
+ }
+
+ // Returns true if this node should exist in the tree.
+ bool ShouldPendingNodeExistInTree(AXNode::AXID node_id) {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ return GetOrCreatePendingStructureChanges(node_id)->node_exists;
+ }
+
+ // Returns the last known node data for a pending node.
+ const AXNodeData& GetLastKnownPendingNodeData(AXNode::AXID node_id) const {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ static base::NoDestructor<ui::AXNodeData> empty_data;
+ PendingStructureChanges* data = GetPendingStructureChanges(node_id);
+ return (data && data->last_known_data) ? *data->last_known_data
+ : *empty_data;
+ }
+
+ // Clear the last known pending data for |node_id|.
+ void ClearLastKnownPendingNodeData(AXNode::AXID node_id) {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ GetOrCreatePendingStructureChanges(node_id)->last_known_data = nullptr;
+ }
+
+ // Update the last known pending node data for |node_data.id|.
+ void SetLastKnownPendingNodeData(const AXNodeData* node_data) {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ GetOrCreatePendingStructureChanges(node_data->id)->last_known_data =
+ node_data;
+ }
+
+ // Returns the number of times the update is expected to destroy a
+ // subtree rooted at |node_id|.
+ int32_t GetPendingDestroySubtreeCount(AXNode::AXID node_id) const {
+ DCHECK(!computing_pending_changes)
+ << "This method should not be called before any updates are made to "
+ "the tree.";
+ if (PendingStructureChanges* data = GetPendingStructureChanges(node_id))
+ return data->destroy_subtree_count;
+ return 0;
+ }
+
+ // Increments the number of times the update is expected to
+ // destroy a subtree rooted at |node_id|.
+ // Returns true on success, false on failure when the node will not exist.
+ bool IncrementPendingDestroySubtreeCount(AXNode::AXID node_id) {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ PendingStructureChanges* data = GetOrCreatePendingStructureChanges(node_id);
+ if (!data->node_exists)
+ return false;
+
+ ++data->destroy_subtree_count;
+ return true;
+ }
+
+ // Decrements the number of times the update is expected to
+ // destroy a subtree rooted at |node_id|.
+ void DecrementPendingDestroySubtreeCount(AXNode::AXID node_id) {
+ DCHECK(!computing_pending_changes)
+ << "This method should not be called before any updates are made to "
+ "the tree.";
+ if (PendingStructureChanges* data = GetPendingStructureChanges(node_id)) {
+ DCHECK_GT(data->destroy_subtree_count, 0);
+ --data->destroy_subtree_count;
+ }
+ }
+
+ // Returns the number of times the update is expected to destroy
+ // a node with |node_id|.
+ int32_t GetPendingDestroyNodeCount(AXNode::AXID node_id) const {
+ DCHECK(!computing_pending_changes)
+ << "This method should not be called before any updates are made to "
+ "the tree.";
+ if (PendingStructureChanges* data = GetPendingStructureChanges(node_id))
+ return data->destroy_node_count;
+ return 0;
+ }
+
+ // Increments the number of times the update is expected to
+ // destroy a node with |node_id|.
+ // Returns true on success, false on failure when the node will not exist.
+ bool IncrementPendingDestroyNodeCount(AXNode::AXID node_id) {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ PendingStructureChanges* data = GetOrCreatePendingStructureChanges(node_id);
+ if (!data->node_exists)
+ return false;
+
+ ++data->destroy_node_count;
+ data->node_exists = false;
+ data->last_known_data = nullptr;
+ data->parent_node_id = base::nullopt;
+ if (pending_root_id == node_id)
+ pending_root_id = base::nullopt;
+ return true;
+ }
+
+ // Decrements the number of times the update is expected to
+ // destroy a node with |node_id|.
+ void DecrementPendingDestroyNodeCount(AXNode::AXID node_id) {
+ DCHECK(!computing_pending_changes)
+ << "This method should not be called before any updates are made to "
+ "the tree.";
+ if (PendingStructureChanges* data = GetPendingStructureChanges(node_id)) {
+ DCHECK_GT(data->destroy_node_count, 0);
+ --data->destroy_node_count;
+ }
+ }
+
+ // Returns the number of times the update is expected to create
+ // a node with |node_id|.
+ int32_t GetPendingCreateNodeCount(AXNode::AXID node_id) const {
+ DCHECK(!computing_pending_changes)
+ << "This method should not be called before any updates are made to "
+ "the tree.";
+ if (PendingStructureChanges* data = GetPendingStructureChanges(node_id))
+ return data->create_node_count;
+ return 0;
+ }
+
+ // Increments the number of times the update is expected to
+ // create a node with |node_id|.
+ // Returns true on success, false on failure when the node will already exist.
+ bool IncrementPendingCreateNodeCount(
+ AXNode::AXID node_id,
+ base::Optional<AXNode::AXID> parent_node_id) {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ PendingStructureChanges* data = GetOrCreatePendingStructureChanges(node_id);
+ if (data->node_exists)
+ return false;
+
+ ++data->create_node_count;
+ data->node_exists = true;
+ data->parent_node_id = parent_node_id;
+ return true;
+ }
+
+ // Decrements the number of times the update is expected to
+ // create a node with |node_id|.
+ void DecrementPendingCreateNodeCount(AXNode::AXID node_id) {
+ DCHECK(!computing_pending_changes)
+ << "This method should not be called before any updates are made to "
+ "the tree.";
+ if (PendingStructureChanges* data = GetPendingStructureChanges(node_id)) {
+ DCHECK_GT(data->create_node_count, 0);
+ --data->create_node_count;
+ }
+ }
+
+ // Returns whether this update must invalidate the unignored cached
+ // values for |node_id|.
+ bool InvalidatesUnignoredCachedValues(AXNode::AXID node_id) {
+ return base::Contains(invalidate_unignored_cached_values_ids, node_id);
+ }
+
+ // Adds the parent of |node_id| to the list of nodes to invalidate unignored
+ // cached values.
+ void InvalidateParentNodeUnignoredCacheValues(AXNode::AXID node_id) {
+ DCHECK(computing_pending_changes) << "This method should be called before "
+ "any updates are made to the tree.";
+ base::Optional<AXNode::AXID> parent_node_id =
+ GetParentIdForPendingNode(node_id);
+ if (parent_node_id) {
+ invalidate_unignored_cached_values_ids.insert(*parent_node_id);
+ }
+ }
+
+ // Indicates if the tree is calculating what changes will occur during
+ // an update before the update applies changes.
+ bool computing_pending_changes;
+
+ // Keeps track of the root node id when calculating what changes will occur
+ // during an update before the update applies changes.
+ base::Optional<AXNode::AXID> pending_root_id;
+
+ // Keeps track of whether the root node will need to be created as a new node.
+ // This may occur either when the root node does not exist before applying
+ // updates to the tree (new tree), or if the root is the |node_id_to_clear|
+ // and will be destroyed before applying AXNodeData updates to the tree.
+ bool root_will_be_created;
+
// During an update, this keeps track of all nodes that have been
// implicitly referenced as part of this update, but haven't been
// updated yet. It's an error if there are any pending nodes at the
// end of Unserialize.
- std::set<const AXNode*> pending_nodes;
-
- // This is similar to above, but we store node ids here because this list gets
- // generated before any nodes get created or re-used. Its purpose is to allow
- // us to know what nodes will be updated so we can make more intelligent
- // decisions about when to notify observers of removals or reparenting.
- std::set<int> changed_node_ids;
+ std::set<AXNode::AXID> pending_nodes;
- // keeps track of nodes whose cached unignored child count, or unignored
+ // Keeps track of nodes whose cached unignored child count, or unignored
// index in parent may have changed, and must be updated.
- std::unordered_set<int> invalidate_unignored_cached_values_ids;
+ std::set<AXNode::AXID> invalidate_unignored_cached_values_ids;
- // Potentially reparented node ids include any child node ids touched by the
- // update, as well as any new root node id. Nodes are considered
- // reparented if they are in this list and removed from somewhere else.
- std::set<int> potentially_reparented_ids;
+ // All child node ids touched by the update, as well as the new root
+ // node id. Nodes are considered reparented if they are in this list
+ // and removed from somewhere else.
+ std::set<AXNode::AXID> node_ids_found_in_update;
- std::unordered_set<int> node_data_changed_ids;
+ // Keeps track of nodes that have changed their node data.
+ std::set<AXNode::AXID> node_data_changed_ids;
// Keeps track of new nodes created during this update.
- std::set<const AXNode*> new_nodes;
-
- // The new root in this update, if any.
- AXNode* new_root;
+ std::set<AXNode::AXID> new_node_ids;
+
+ // Keeps track of any nodes removed. Nodes are removed when their AXID no
+ // longer exist in the parent |child_ids| list, or the node is part of to the
+ // subtree of the AXID that was explicitally cleared with |node_id_to_clear|.
+ // Used to identify re-parented nodes. A re-parented occurs when any AXID
+ // is first removed from the tree then added to the tree again.
+ std::set<AXNode::AXID> removed_node_ids;
+
+ // Maps between a node id and its pending update information.
+ std::map<AXNode::AXID, std::unique_ptr<PendingStructureChanges>>
+ node_id_to_pending_data;
+
+ // Maps between a node id and the data it owned before being updated.
+ // We need to keep this around in order to correctly fire post-update events.
+ std::map<AXNode::AXID, AXNodeData> old_node_id_to_data;
+
+ // Optional copy of the old tree data, only populated when the tree
+ // data has changed.
+ base::Optional<AXTreeData> old_tree_data;
+
+ private:
+ PendingStructureChanges* GetPendingStructureChanges(
+ AXNode::AXID node_id) const {
+ auto iter = node_id_to_pending_data.find(node_id);
+ return (iter != node_id_to_pending_data.cend()) ? iter->second.get()
+ : nullptr;
+ }
- // Keeps track of any nodes removed. Used to identify re-parented nodes.
- std::set<int> removed_node_ids;
+ PendingStructureChanges* GetOrCreatePendingStructureChanges(
+ AXNode::AXID node_id) {
+ auto iter = node_id_to_pending_data.find(node_id);
+ if (iter == node_id_to_pending_data.cend()) {
+ const AXNode* node = tree.GetFromId(node_id);
+ iter = node_id_to_pending_data
+ .emplace(std::make_pair(
+ node_id, std::make_unique<PendingStructureChanges>(node)))
+ .first;
+ }
+ return iter->second.get();
+ }
- // Maps between a node id and its data. We need to keep this around because
- // the reparented nodes in this update were actually deleted.
- std::map<int32_t, AXNodeData> reparented_node_id_to_data;
+ // We need to hold onto a reference to the AXTree so that we can
+ // lazily initialize |PendingStructureChanges| objects.
+ const AXTree& tree;
};
AXTree::AXTree() {
AXNodeData root;
- root.id = -1;
+ root.id = AXNode::kInvalidAXID;
AXTreeUpdate initial_state;
- initial_state.root_id = -1;
+ initial_state.root_id = AXNode::kInvalidAXID;
initial_state.nodes.push_back(root);
CHECK(Unserialize(initial_state)) << error();
// TODO(chrishall): should language_detection_manager be a member or pointer?
@@ -188,8 +555,12 @@ AXTree::AXTree(const AXTreeUpdate& initial_state) {
}
AXTree::~AXTree() {
- if (root_)
+ if (root_) {
+ RecursivelyNotifyNodeWillBeDeleted(root_);
+ base::AutoReset<bool> update_state_resetter(&tree_update_in_progress_,
+ true);
DestroyNodeAndSubtree(root_, nullptr);
+ }
for (auto& entry : table_info_map_)
delete entry.second;
table_info_map_.clear();
@@ -398,65 +769,114 @@ const std::set<AXTreeID> AXTree::GetAllChildTreeIds() const {
}
bool AXTree::Unserialize(const AXTreeUpdate& update) {
- // Set update state to true.
- // tree_update_in_progress_ gets set back to false whenever this function
- // exits.
- base::AutoReset<bool> update_state_resetter(&tree_update_in_progress_, true);
-
- AXTreeUpdateState update_state;
- int32_t old_root_id = root_ ? root_->id() : 0;
-
- // First, make a note of any nodes we will touch as part of this update.
- for (size_t i = 0; i < update.nodes.size(); ++i)
- update_state.changed_node_ids.insert(update.nodes[i].id);
-
- if (update.has_tree_data)
- UpdateData(update.tree_data);
+ AXTreeUpdateState update_state(*this);
+ const AXNode::AXID old_root_id = root_ ? root_->id() : AXNode::kInvalidAXID;
// Get all of the node ids that are certain to exist after the update.
// These are the nodes that are considered reparented if they are removed from
// somewhere else.
- update_state.potentially_reparented_ids.emplace(update.root_id);
+ if (update.root_id != AXNode::kInvalidAXID)
+ update_state.node_ids_found_in_update.emplace(update.root_id);
for (const AXNodeData& update_node_data : update.nodes) {
- update_state.potentially_reparented_ids.insert(
+ update_state.node_ids_found_in_update.insert(
update_node_data.child_ids.begin(), update_node_data.child_ids.end());
}
+ // Accumulates the work that will be required to update the AXTree.
+ // This allows us to notify observers of structure changes when the
+ // tree is still in a stable and unchanged state.
+ if (!ComputePendingChanges(update, update_state))
+ return false;
+
+ // Notify observers of subtrees and nodes that are about to be destroyed or
+ // reparented, this must be done before applying any updates to the tree.
+ for (auto&& pair : update_state.node_id_to_pending_data) {
+ const AXNode::AXID node_id = pair.first;
+ const std::unique_ptr<PendingStructureChanges>& data = pair.second;
+ if (data->DoesNodeExpectSubtreeOrNodeWillBeDestroyed()) {
+ if (AXNode* node = GetFromId(node_id)) {
+ if (data->DoesNodeExpectSubtreeWillBeDestroyed())
+ NotifySubtreeWillBeReparentedOrDeleted(node, &update_state);
+ if (data->DoesNodeExpectNodeWillBeDestroyed())
+ NotifyNodeWillBeReparentedOrDeleted(node, &update_state);
+ }
+ }
+ }
+
+ // Notify observers of nodes that are about to change their data.
+ // This must be done before applying any updates to the tree.
+ // This is iterating in reverse order so that we only notify once per node id,
+ // and that we only notify the initial node data against the final node data,
+ // unless the node is a new root.
+ std::set<int32_t> notified_node_data_will_change;
+ for (size_t i = update.nodes.size(); i-- > 0;) {
+ const AXNodeData& new_data = update.nodes[i];
+ const bool is_new_root =
+ update_state.root_will_be_created && new_data.id == update.root_id;
+ if (!is_new_root) {
+ AXNode* node = GetFromId(new_data.id);
+ if (node && notified_node_data_will_change.insert(new_data.id).second)
+ NotifyNodeDataWillChange(node->data(), new_data);
+ }
+ }
+
+ // Now that we have finished sending events for changes that will happen,
+ // set update state to true. |tree_update_in_progress_| gets set back to
+ // false whenever this function exits.
+ base::AutoReset<bool> update_state_resetter(&tree_update_in_progress_, true);
+
+ // Handle |node_id_to_clear| before applying ordinary node updates.
// We distinguish between updating the root, e.g. changing its children or
- // some of its attributes, or replacing the root completely.
+ // some of its attributes, or replacing the root completely. If the root is
+ // being updated, update.node_id_to_clear should hold the current root's ID.
+ // Otherwise if the root is being replaced, update.root_id should hold the ID
+ // of the new root.
bool root_updated = false;
- if (update.node_id_to_clear != 0) {
- AXNode* node = GetFromId(update.node_id_to_clear);
-
- // Only destroy the root if the root was replaced and not if it's simply
- // updated. To figure out if the root was simply updated, we compare the ID
- // of the new root with the existing root ID.
- if (node && node == root_) {
- if (update.root_id != old_root_id) {
- // Clear root_ before calling DestroySubtree so that root_ doesn't ever
- // point to an invalid node.
- AXNode* old_root = root_;
- root_ = nullptr;
- DestroySubtree(old_root, &update_state);
- } else {
- root_updated = true;
+ if (update.node_id_to_clear != AXNode::kInvalidAXID) {
+ if (AXNode* cleared_node = GetFromId(update.node_id_to_clear)) {
+ DCHECK(root_);
+ if (cleared_node == root_) {
+ // Only destroy the root if the root was replaced and not if it's simply
+ // updated. To figure out if the root was simply updated, we compare
+ // the ID of the new root with the existing root ID.
+ if (update.root_id != old_root_id) {
+ // Clear root_ before calling DestroySubtree so that root_ doesn't
+ // ever point to an invalid node.
+ AXNode* old_root = root_;
+ root_ = nullptr;
+ DestroySubtree(old_root, &update_state);
+ } else {
+ // If the root has simply been updated, we treat it like an update to
+ // any other node.
+ root_updated = true;
+ }
}
- }
- // If the root has simply been updated, we treat it like an update to any
- // other node.
- if (node && root_ && (node != root_ || root_updated)) {
- for (auto* child : node->children())
- DestroySubtree(child, &update_state);
- std::vector<AXNode*> children;
- node->SwapChildren(children);
- update_state.pending_nodes.insert(node);
+ // If the tree doesn't exists any more because the root has just been
+ // replaced, there is nothing more to clear.
+ if (root_) {
+ for (auto* child : cleared_node->children())
+ DestroySubtree(child, &update_state);
+ std::vector<AXNode*> children;
+ cleared_node->SwapChildren(children);
+ update_state.pending_nodes.insert(cleared_node->id());
+ }
}
}
- bool root_exists = GetFromId(update.root_id) != nullptr;
+ DCHECK_EQ(!GetFromId(update.root_id), update_state.root_will_be_created);
+
+ // Update the tree data, do not call |UpdateData| since we want to defer
+ // the |OnTreeDataChanged| event until after the tree has finished updating.
+ if (update.has_tree_data && data_ != update.tree_data) {
+ update_state.old_tree_data = data_;
+ data_ = update.tree_data;
+ }
+
+ // Update all of the nodes in the update.
for (size_t i = 0; i < update.nodes.size(); ++i) {
- bool is_new_root = !root_exists && update.nodes[i].id == update.root_id;
+ const bool is_new_root = update_state.root_will_be_created &&
+ update.nodes[i].id == update.root_id;
if (!UpdateNode(update.nodes[i], is_new_root, &update_state))
return false;
}
@@ -466,12 +886,8 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
return false;
}
- if (!update_state.pending_nodes.empty()) {
- error_ = "Nodes left pending by the update:";
- for (const AXNode* pending : update_state.pending_nodes)
- error_ += base::StringPrintf(" %d", pending->id());
+ if (!ValidatePendingChangesComplete(update_state))
return false;
- }
// Look for changes to nodes that are a descendant of a table,
// and invalidate their table info if so. We have to walk up the
@@ -495,7 +911,6 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
// Clear list_info_map_
ordered_set_info_map_.clear();
- std::set<const AXNode*>& new_nodes = update_state.new_nodes;
std::vector<AXTreeObserver::Change> changes;
changes.reserve(update.nodes.size());
for (size_t i = 0; i < update.nodes.size(); ++i) {
@@ -503,7 +918,7 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
if (!node)
continue;
- bool is_new_node = update_state.IsNewNode(node);
+ bool is_new_node = update_state.IsCreatedNode(node);
bool is_reparented_node = update_state.IsReparentedNode(node);
AXTreeObserver::ChangeType change = AXTreeObserver::NODE_CHANGED;
@@ -514,7 +929,7 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
// Note that we also need to check for the special case when we update
// the root without replacing it.
bool is_subtree = !node->parent() ||
- new_nodes.find(node->parent()) == new_nodes.end() ||
+ !update_state.IsCreatedNode(node->parent()) ||
(node->parent() == root_ && root_updated);
change = is_subtree ? AXTreeObserver::SUBTREE_REPARENTED
: AXTreeObserver::NODE_REPARENTED;
@@ -524,7 +939,7 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
// Note that we also need to check for the special case when we update
// the root without replacing it.
bool is_subtree = !node->parent() ||
- new_nodes.find(node->parent()) == new_nodes.end() ||
+ !update_state.IsCreatedNode(node->parent()) ||
update_state.IsRemovedNode(node->parent()) ||
(node->parent() == root_ && root_updated);
change = is_subtree ? AXTreeObserver::SUBTREE_CREATED
@@ -534,25 +949,58 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
changes.push_back(AXTreeObserver::Change(node, change));
}
- // Update the unignored cached values as necessary.
- for (int parent_id : update_state.invalidate_unignored_cached_values_ids) {
- AXNode* parent = GetFromId(parent_id);
- if (parent)
- parent->UpdateUnignoredCachedValues();
+ // Update the unignored cached values as necessary, ensuring that we only
+ // update once for each unignored node.
+ // If the node is ignored, we must update from an unignored ancestor.
+ std::set<AXNode::AXID> updated_unignored_cached_values_ids;
+ for (AXNode::AXID node_id :
+ update_state.invalidate_unignored_cached_values_ids) {
+ AXNode* node = GetFromId(node_id);
+ while (node && node->data().HasState(ax::mojom::State::kIgnored))
+ node = node->parent();
+ if (node && updated_unignored_cached_values_ids.insert(node->id()).second)
+ node->UpdateUnignoredCachedValues();
+ }
+
+ // Tree is no longer updating.
+ SetTreeUpdateInProgressState(false);
+
+ // Now that the tree is stable and its nodes have been updated, notify if
+ // the tree data changed. We must do this after updating nodes in case the
+ // root has been replaced, so observers have the most up-to-date information.
+ if (update_state.old_tree_data) {
+ for (AXTreeObserver& observer : observers_)
+ observer.OnTreeDataChanged(this, *update_state.old_tree_data, data_);
+ }
+
+ // Now that the unignored cached values are up to date, update observers to
+ // new nodes in the tree.
+ for (AXNode::AXID node_id : update_state.new_node_ids) {
+ NotifyNodeHasBeenReparentedOrCreated(GetFromId(node_id), &update_state);
}
// Now that the unignored cached values are up to date, update observers to
// node changes.
- for (int node_data_changed_id : update_state.node_data_changed_ids) {
+ for (AXNode::AXID node_data_changed_id : update_state.node_data_changed_ids) {
AXNode* node = GetFromId(node_data_changed_id);
- if (node) {
- for (AXTreeObserver& observer : observers_)
- observer.OnNodeChanged(this, node);
+ DCHECK(node);
+
+ // If the node exists and is in the old data map, then the node data
+ // may have changed unless this is a new root.
+ const bool is_new_root = update_state.root_will_be_created &&
+ node_data_changed_id == update.root_id;
+ if (!is_new_root) {
+ auto it = update_state.old_node_id_to_data.find(node_data_changed_id);
+ if (it != update_state.old_node_id_to_data.end()) {
+ const AXNodeData& old_node_data = it->second;
+ NotifyNodeDataHasBeenChanged(node, old_node_data, node->data());
+ }
}
- }
- // Tree is no longer updating.
- SetTreeUpdateInProgressState(false);
+ // |OnNodeChanged| should be fired for all nodes that have been updated.
+ for (AXTreeObserver& observer : observers_)
+ observer.OnNodeChanged(this, node);
+ }
for (AXTreeObserver& observer : observers_) {
observer.OnAtomicUpdateFinished(this, root_->id() != old_root_id, changes);
@@ -562,6 +1010,7 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
}
AXTableInfo* AXTree::GetTableInfo(const AXNode* const_table_node) const {
+ DCHECK(!GetTreeUpdateInProgressState());
// Note: the const_casts are here because we want this function to be able
// to be called from a const virtual function on AXNode. AXTableInfo is
// computed on demand and cached, but that's an implementation detail
@@ -607,31 +1056,211 @@ std::string AXTree::ToString() const {
}
AXNode* AXTree::CreateNode(AXNode* parent,
- int32_t id,
+ AXNode::AXID id,
size_t index_in_parent,
AXTreeUpdateState* update_state) {
+ DCHECK(GetTreeUpdateInProgressState());
+ // |update_state| must already contain information about all of the expected
+ // changes and invalidations to apply. If any of these are missing, observers
+ // may not be notified of changes.
+ DCHECK(!GetFromId(id));
+ DCHECK_GT(update_state->GetPendingCreateNodeCount(id), 0);
+ DCHECK(update_state->InvalidatesUnignoredCachedValues(id));
+ DCHECK(!parent ||
+ update_state->InvalidatesUnignoredCachedValues(parent->id()));
+ update_state->DecrementPendingCreateNodeCount(id);
+ update_state->new_node_ids.insert(id);
// If this node is the root, use the given index_in_parent as the unignored
// index in parent to provide consistency with index_in_parent.
AXNode* new_node = new AXNode(this, parent, id, index_in_parent,
parent ? 0 : index_in_parent);
id_map_[new_node->id()] = new_node;
- for (AXTreeObserver& observer : observers_) {
- if (update_state->IsReparentedNode(new_node))
- observer.OnNodeReparented(this, new_node);
- else
- observer.OnNodeCreated(this, new_node);
+ return new_node;
+}
+
+bool AXTree::ComputePendingChanges(const AXTreeUpdate& update,
+ AXTreeUpdateState& update_state) {
+ base::AutoReset<bool> computing_pending_changes_resetter(
+ &update_state.computing_pending_changes, true);
+ base::AutoReset<base::Optional<AXNode::AXID>> pending_root_id_resetter(
+ &update_state.pending_root_id,
+ root_ ? base::Optional<AXNode::AXID>{root_->id()} : base::nullopt);
+
+ // We distinguish between updating the root, e.g. changing its children or
+ // some of its attributes, or replacing the root completely. If the root is
+ // being updated, update.node_id_to_clear should hold the current root's ID.
+ // Otherwise if the root is being replaced, update.root_id should hold the ID
+ // of the new root.
+ if (update.node_id_to_clear != AXNode::kInvalidAXID) {
+ if (AXNode* cleared_node = GetFromId(update.node_id_to_clear)) {
+ DCHECK(root_);
+ if (cleared_node == root_ &&
+ update.root_id != update_state.pending_root_id) {
+ // Only destroy the root if the root was replaced and not if it's simply
+ // updated. To figure out if the root was simply updated, we compare
+ // the ID of the new root with the existing root ID.
+ MarkSubtreeForDestruction(*update_state.pending_root_id, &update_state);
+ }
+
+ // If the tree has been marked for destruction because the root will be
+ // replaced, there is nothing more to clear.
+ if (update_state.ShouldPendingNodeExistInTree(root_->id())) {
+ update_state.invalidate_unignored_cached_values_ids.insert(
+ cleared_node->id());
+ update_state.ClearLastKnownPendingNodeData(cleared_node->id());
+ for (AXNode* child : cleared_node->children()) {
+ MarkSubtreeForDestruction(child->id(), &update_state);
+ }
+ }
+ }
}
- AXNode* unignored_parent = new_node->GetUnignoredParent();
- if (unignored_parent) {
- update_state->invalidate_unignored_cached_values_ids.insert(
- unignored_parent->id());
+
+ update_state.root_will_be_created =
+ !GetFromId(update.root_id) ||
+ !update_state.ShouldPendingNodeExistInTree(update.root_id);
+
+ // Populate |update_state| with all of the changes that will be performed
+ // on the tree during the update.
+ for (const AXNodeData& new_data : update.nodes) {
+ bool is_new_root =
+ update_state.root_will_be_created && new_data.id == update.root_id;
+ if (!ComputePendingChangesToNode(new_data, is_new_root, &update_state)) {
+ return false;
+ }
}
- return new_node;
+
+ return true;
+}
+
+bool AXTree::ComputePendingChangesToNode(const AXNodeData& new_data,
+ bool is_new_root,
+ AXTreeUpdateState* update_state) {
+ // If the node does not exist in the tree throw an error unless this
+ // is the new root and it can be created.
+ if (!update_state->ShouldPendingNodeExistInTree(new_data.id)) {
+ if (!is_new_root) {
+ error_ = base::StringPrintf(
+ "%d will not be in the tree and is not the new root", new_data.id);
+ return false;
+ }
+
+ // Creation is implicit for new root nodes. If |new_data.id| is already
+ // pending for creation, then it must be a duplicate entry in the tree.
+ if (!update_state->IncrementPendingCreateNodeCount(new_data.id,
+ base::nullopt)) {
+ error_ = base::StringPrintf(
+ "Node %d is already pending for creation, cannot be the new root",
+ new_data.id);
+ return false;
+ }
+ if (update_state->pending_root_id) {
+ MarkSubtreeForDestruction(*update_state->pending_root_id, update_state);
+ }
+ update_state->pending_root_id = new_data.id;
+ }
+
+ // Create a set of new child ids so we can use it to find the nodes that
+ // have been added and removed. Returns false if a duplicate is found.
+ std::set<AXNode::AXID> new_child_id_set;
+ for (AXNode::AXID new_child_id : new_data.child_ids) {
+ if (base::Contains(new_child_id_set, new_child_id)) {
+ error_ = base::StringPrintf("Node %d has duplicate child id %d",
+ new_data.id, new_child_id);
+ return false;
+ }
+ new_child_id_set.insert(new_child_id);
+ }
+
+ // If the node has not been initialized yet then its node data has either been
+ // cleared when handling |node_id_to_clear|, or it's a new node.
+ // In either case, all children must be created.
+ if (update_state->DoesPendingNodeRequireInit(new_data.id)) {
+ update_state->invalidate_unignored_cached_values_ids.insert(new_data.id);
+
+ // If this node has been cleared via |node_id_to_clear| or is a new node,
+ // the last-known parent's unignored cache needs to be updated.
+ update_state->InvalidateParentNodeUnignoredCacheValues(new_data.id);
+
+ for (AXNode::AXID child_id : new_child_id_set) {
+ // If a |child_id| is already pending for creation, then it must be a
+ // duplicate entry in the tree.
+ update_state->invalidate_unignored_cached_values_ids.insert(child_id);
+ if (!update_state->IncrementPendingCreateNodeCount(child_id,
+ new_data.id)) {
+ error_ = base::StringPrintf(
+ "Node %d is already pending for creation, cannot be a new child",
+ child_id);
+ return false;
+ }
+ }
+
+ update_state->SetLastKnownPendingNodeData(&new_data);
+ return true;
+ }
+
+ const AXNodeData& old_data =
+ update_state->GetLastKnownPendingNodeData(new_data.id);
+
+ // Create a set of old child ids so we can use it to find the nodes that
+ // have been added and removed.
+ std::set<AXNode::AXID> old_child_id_set(old_data.child_ids.cbegin(),
+ old_data.child_ids.cend());
+
+ std::vector<AXNode::AXID> create_or_destroy_ids;
+ std::set_symmetric_difference(
+ old_child_id_set.cbegin(), old_child_id_set.cend(),
+ new_child_id_set.cbegin(), new_child_id_set.cend(),
+ std::back_inserter(create_or_destroy_ids));
+
+ // If the node has changed ignored state or there are any differences in
+ // its children, then its unignored cached values must be invalidated.
+ const bool ignored_changed = old_data.HasState(ax::mojom::State::kIgnored) !=
+ new_data.HasState(ax::mojom::State::kIgnored);
+ if (!create_or_destroy_ids.empty() || ignored_changed) {
+ update_state->invalidate_unignored_cached_values_ids.insert(new_data.id);
+
+ // If this ignored state had changed also invalidate the parent.
+ update_state->InvalidateParentNodeUnignoredCacheValues(new_data.id);
+ }
+
+ for (AXNode::AXID child_id : create_or_destroy_ids) {
+ if (base::Contains(new_child_id_set, child_id)) {
+ // This is a serious error - nodes should never be reparented without
+ // first being removed from the tree. If a node exists in the tree already
+ // then adding it to a new parent would mean stealing the node from its
+ // old parent which hadn't been updated to reflect the change.
+ if (update_state->ShouldPendingNodeExistInTree(child_id)) {
+ error_ = base::StringPrintf(
+ "Node %d is not marked for destruction, would be reparented to %d",
+ child_id, new_data.id);
+ return false;
+ }
+
+ // If a |child_id| is already pending for creation, then it must be a
+ // duplicate entry in the tree.
+ update_state->invalidate_unignored_cached_values_ids.insert(child_id);
+ if (!update_state->IncrementPendingCreateNodeCount(child_id,
+ new_data.id)) {
+ error_ = base::StringPrintf(
+ "Node %d is already pending for creation, cannot be a new child",
+ child_id);
+ return false;
+ }
+ } else {
+ // If |child_id| does not exist in the new set, then it has
+ // been removed from |node|, and the subtree must be deleted.
+ MarkSubtreeForDestruction(child_id, update_state);
+ }
+ }
+
+ update_state->SetLastKnownPendingNodeData(&new_data);
+ return true;
}
bool AXTree::UpdateNode(const AXNodeData& src,
bool is_new_root,
AXTreeUpdateState* update_state) {
+ DCHECK(GetTreeUpdateInProgressState());
// This method updates one node in the tree based on serialized data
// received in an AXTreeUpdate. See AXTreeUpdate for pre and post
// conditions.
@@ -641,51 +1270,13 @@ bool AXTree::UpdateNode(const AXNodeData& src,
// and this is a serious error.
AXNode* node = GetFromId(src.id);
if (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->IsNewNode(node) ||
+ update_state->pending_nodes.erase(node->id());
+ UpdateReverseRelations(node, src);
+ if (!update_state->IsCreatedNode(node) ||
update_state->IsReparentedNode(node)) {
- auto it = update_state->reparented_node_id_to_data.find(node->id());
- const AXNodeData& old_data =
- it != update_state->reparented_node_id_to_data.end() ? it->second
- : node->data();
- CallNodeChangeCallbacks(node, old_data, src);
- if (old_data.HasState(ax::mojom::State::kIgnored) !=
- src.HasState(ax::mojom::State::kIgnored)) {
- AXNode* unignored_parent = node->GetUnignoredParent();
- if (unignored_parent) {
- update_state->invalidate_unignored_cached_values_ids.insert(
- unignored_parent->id());
- }
-
- // We must invalidate the node if it's no longer State::kIgnored.
- // Since nodes updated in no particular order, this node may
- // not be added to the set later or update its cached values.
- //
- // For example, given the following tree, :
- // A unignored
- // |
- // B ignored
- // |
- // C unignored
- //
- // ... and the following updates :
- // Update C unignored => ignored
- // Update B ignored => unignored
- //
- // Both updates would add A to the set of nodes which have invalid
- // cached values, but B would never be added because ignored nodes
- // are skipped over.
- if (!src.HasState(ax::mojom::State::kIgnored)) {
- update_state->invalidate_unignored_cached_values_ids.insert(
- node->id());
- }
- }
+ update_state->old_node_id_to_data.insert(
+ std::make_pair(node->id(), node->TakeData()));
}
- UpdateReverseRelations(node, src);
node->SetData(src);
} else {
if (!is_new_root) {
@@ -694,9 +1285,7 @@ bool AXTree::UpdateNode(const AXNodeData& src,
return false;
}
- update_state->new_root = CreateNode(nullptr, src.id, 0, update_state);
- node = update_state->new_root;
- update_state->new_nodes.insert(node);
+ node = CreateNode(nullptr, src.id, 0, update_state);
UpdateReverseRelations(node, src);
node->SetData(src);
}
@@ -705,26 +1294,7 @@ bool AXTree::UpdateNode(const AXNodeData& src,
// First, delete nodes that used to be children of this node but aren't
// anymore.
- if (!DeleteOldChildren(node, src.child_ids, update_state)) {
- // If DeleteOldChildren failed, we need to carefully clean up before
- // returning false as well. In particular, if this node was a new root,
- // we need to safely destroy the whole tree.
- if (update_state->new_root) {
- AXNode* old_root = root_;
- root_ = nullptr;
-
- DestroySubtree(old_root, update_state);
-
- // Delete |node|'s subtree too as long as it wasn't already removed
- // or added elsewhere in the tree.
- if (update_state->removed_node_ids.find(src.id) ==
- update_state->removed_node_ids.end() &&
- update_state->new_nodes.find(node) != update_state->new_nodes.end()) {
- DestroySubtree(node, update_state);
- }
- }
- return false;
- }
+ DeleteOldChildren(node, src.child_ids, update_state);
// Now build a new children vector, reusing nodes when possible,
// and swap it in.
@@ -746,11 +1316,84 @@ bool AXTree::UpdateNode(const AXNodeData& src,
return success;
}
-void AXTree::CallNodeChangeCallbacks(AXNode* node,
- const AXNodeData& old_data,
- const AXNodeData& new_data) {
+void AXTree::NotifySubtreeWillBeReparentedOrDeleted(
+ AXNode* node,
+ const AXTreeUpdateState* update_state) {
+ DCHECK(!GetTreeUpdateInProgressState());
+ if (node->id() == AXNode::kInvalidAXID)
+ return;
+
+ for (AXTreeObserver& observer : observers_) {
+ if (update_state->IsPotentiallyReparentedNode(node)) {
+ observer.OnSubtreeWillBeReparented(this, node);
+ } else {
+ observer.OnSubtreeWillBeDeleted(this, node);
+ }
+ }
+}
+
+void AXTree::NotifyNodeWillBeReparentedOrDeleted(
+ AXNode* node,
+ const AXTreeUpdateState* update_state) {
+ DCHECK(!GetTreeUpdateInProgressState());
+ if (node->id() == AXNode::kInvalidAXID)
+ return;
+
+ for (AXTreeObserver& observer : observers_) {
+ if (update_state->IsPotentiallyReparentedNode(node)) {
+ observer.OnNodeWillBeReparented(this, node);
+ } else {
+ observer.OnNodeWillBeDeleted(this, node);
+ }
+ }
+}
+
+void AXTree::RecursivelyNotifyNodeWillBeDeleted(AXNode* node) {
+ DCHECK(!GetTreeUpdateInProgressState());
+ if (node->id() == AXNode::kInvalidAXID)
+ return;
+
+ for (AXTreeObserver& observer : observers_)
+ observer.OnNodeWillBeDeleted(this, node);
+ for (auto* child : node->children())
+ RecursivelyNotifyNodeWillBeDeleted(child);
+}
+
+void AXTree::NotifyNodeHasBeenReparentedOrCreated(
+ AXNode* node,
+ const AXTreeUpdateState* update_state) {
+ DCHECK(!GetTreeUpdateInProgressState());
+ if (node->id() == AXNode::kInvalidAXID)
+ return;
+
+ for (AXTreeObserver& observer : observers_) {
+ if (update_state->IsReparentedNode(node)) {
+ observer.OnNodeReparented(this, node);
+ } else {
+ observer.OnNodeCreated(this, node);
+ }
+ }
+}
+
+void AXTree::NotifyNodeDataWillChange(const AXNodeData& old_data,
+ const AXNodeData& new_data) {
+ DCHECK(!GetTreeUpdateInProgressState());
+ if (new_data.id == AXNode::kInvalidAXID)
+ return;
+
for (AXTreeObserver& observer : observers_)
observer.OnNodeDataWillChange(this, old_data, new_data);
+}
+
+void AXTree::NotifyNodeDataHasBeenChanged(AXNode* node,
+ const AXNodeData& old_data,
+ const AXNodeData& new_data) {
+ DCHECK(!GetTreeUpdateInProgressState());
+ if (node->id() == AXNode::kInvalidAXID)
+ return;
+
+ for (AXTreeObserver& observer : observers_)
+ observer.OnNodeDataChanged(this, old_data, new_data);
if (old_data.role != new_data.role) {
for (AXTreeObserver& observer : observers_)
@@ -832,6 +1475,7 @@ void AXTree::CallNodeChangeCallbacks(AXNode* node,
}
void AXTree::UpdateReverseRelations(AXNode* node, const AXNodeData& new_data) {
+ DCHECK(GetTreeUpdateInProgressState());
const AXNodeData& old_data = node->data();
int id = new_data.id;
auto int_callback = [this, id](ax::mojom::IntAttribute attr,
@@ -905,20 +1549,89 @@ void AXTree::UpdateReverseRelations(AXNode* node, const AXNodeData& new_data) {
string_callback);
}
+bool AXTree::ValidatePendingChangesComplete(
+ const AXTreeUpdateState& update_state) {
+ if (!update_state.pending_nodes.empty()) {
+ error_ = "Nodes left pending by the update:";
+ for (const AXNode::AXID pending_id : update_state.pending_nodes)
+ error_ += base::StringPrintf(" %d", pending_id);
+ return false;
+ }
+
+ if (!update_state.node_id_to_pending_data.empty()) {
+ std::string destroy_subtree_ids;
+ std::string destroy_node_ids;
+ std::string create_node_ids;
+
+ bool has_pending_changes = false;
+ for (auto&& pair : update_state.node_id_to_pending_data) {
+ const AXNode::AXID pending_id = pair.first;
+ const std::unique_ptr<PendingStructureChanges>& data = pair.second;
+ if (data->DoesNodeExpectAnyStructureChanges()) {
+ if (data->DoesNodeExpectSubtreeWillBeDestroyed())
+ destroy_subtree_ids += base::StringPrintf(" %d", pending_id);
+ if (data->DoesNodeExpectNodeWillBeDestroyed())
+ destroy_node_ids += base::StringPrintf(" %d", pending_id);
+ if (data->DoesNodeExpectNodeWillBeCreated())
+ create_node_ids += base::StringPrintf(" %d", pending_id);
+ has_pending_changes = true;
+ }
+ }
+ if (has_pending_changes) {
+ error_ = base::StringPrintf(
+ "Changes left pending by the update; "
+ "destroy subtrees: %s, destroy nodes: %s, create nodes: %s",
+ destroy_subtree_ids.c_str(), destroy_node_ids.c_str(),
+ create_node_ids.c_str());
+ }
+ return !has_pending_changes;
+ }
+
+ return true;
+}
+
+void AXTree::MarkSubtreeForDestruction(AXNode::AXID node_id,
+ AXTreeUpdateState* update_state) {
+ update_state->IncrementPendingDestroySubtreeCount(node_id);
+ MarkNodesForDestructionRecursive(node_id, update_state);
+}
+
+void AXTree::MarkNodesForDestructionRecursive(AXNode::AXID node_id,
+ AXTreeUpdateState* update_state) {
+ // If this subtree has already been marked for destruction, return so
+ // we don't walk it again.
+ if (!update_state->ShouldPendingNodeExistInTree(node_id))
+ return;
+
+ const AXNodeData& last_known_data =
+ update_state->GetLastKnownPendingNodeData(node_id);
+
+ update_state->IncrementPendingDestroyNodeCount(node_id);
+ for (AXNode::AXID child_id : last_known_data.child_ids) {
+ MarkNodesForDestructionRecursive(child_id, update_state);
+ }
+}
+
void AXTree::DestroySubtree(AXNode* node,
AXTreeUpdateState* update_state) {
+ DCHECK(GetTreeUpdateInProgressState());
+ // |update_state| must already contain information about all of the expected
+ // changes and invalidations to apply. If any of these are missing, observers
+ // may not be notified of changes.
DCHECK(update_state);
- for (AXTreeObserver& observer : observers_) {
- if (update_state->IsPotentiallyReparentedNode(node))
- observer.OnSubtreeWillBeReparented(this, node);
- else
- observer.OnSubtreeWillBeDeleted(this, node);
- }
+ DCHECK_GT(update_state->GetPendingDestroySubtreeCount(node->id()), 0);
+ DCHECK(!node->parent() ||
+ update_state->InvalidatesUnignoredCachedValues(node->parent()->id()));
+ update_state->DecrementPendingDestroySubtreeCount(node->id());
DestroyNodeAndSubtree(node, update_state);
}
void AXTree::DestroyNodeAndSubtree(AXNode* node,
AXTreeUpdateState* update_state) {
+ DCHECK(GetTreeUpdateInProgressState());
+ DCHECK(!update_state ||
+ update_state->GetPendingDestroyNodeCount(node->id()) > 0);
+
// Clear out any reverse relations.
AXNodeData empty_data;
empty_data.id = node->id();
@@ -931,62 +1644,46 @@ void AXTree::DestroyNodeAndSubtree(AXNode* node,
table_info_map_.erase(node->id());
}
- for (AXTreeObserver& observer : observers_) {
- if (update_state && update_state->IsPotentiallyReparentedNode(node))
- observer.OnNodeWillBeReparented(this, node);
- else
- observer.OnNodeWillBeDeleted(this, node);
- }
id_map_.erase(node->id());
for (auto* child : node->children())
DestroyNodeAndSubtree(child, update_state);
if (update_state) {
- AXNode* unignored_parent = node->GetUnignoredParent();
- if (unignored_parent) {
- update_state->invalidate_unignored_cached_values_ids.insert(
- unignored_parent->id());
- }
- update_state->pending_nodes.erase(node);
+ update_state->pending_nodes.erase(node->id());
+ update_state->DecrementPendingDestroyNodeCount(node->id());
update_state->removed_node_ids.insert(node->id());
- }
-
- if (update_state && update_state->IsReparentedNode(node)) {
- update_state->reparented_node_id_to_data.insert(
- std::make_pair(node->id(), node->TakeData()));
+ update_state->new_node_ids.erase(node->id());
+ update_state->node_data_changed_ids.erase(node->id());
+ if (update_state->IsReparentedNode(node)) {
+ update_state->old_node_id_to_data.emplace(
+ std::make_pair(node->id(), node->TakeData()));
+ }
}
node->Destroy();
}
-bool AXTree::DeleteOldChildren(AXNode* node,
+void AXTree::DeleteOldChildren(AXNode* node,
const std::vector<int32_t>& new_child_ids,
AXTreeUpdateState* update_state) {
- // Create a set of child ids in |src| for fast lookup, and return false
- // if a duplicate is found;
- std::set<int32_t> new_child_id_set;
- for (size_t i = 0; i < new_child_ids.size(); ++i) {
- if (new_child_id_set.find(new_child_ids[i]) != new_child_id_set.end()) {
- error_ = base::StringPrintf("Node %d has duplicate child id %d",
- node->id(), new_child_ids[i]);
- return false;
- }
- new_child_id_set.insert(new_child_ids[i]);
- }
+ DCHECK(GetTreeUpdateInProgressState());
+ // Create a set of child ids in |src| for fast lookup, we know the set does
+ // not contain duplicate entries already, because that was handled when
+ // populating |update_state| with information about all of the expected
+ // changes to be applied.
+ std::set<int32_t> new_child_id_set(new_child_ids.begin(),
+ new_child_ids.end());
// Delete the old children.
- const std::vector<AXNode*>& old_children = node->children();
- for (size_t i = 0; i < old_children.size(); ++i) {
- int old_id = old_children[i]->id();
- if (new_child_id_set.find(old_id) == new_child_id_set.end())
- DestroySubtree(old_children[i], update_state);
+ for (AXNode* child : node->children()) {
+ if (!base::Contains(new_child_id_set, child->id()))
+ DestroySubtree(child, update_state);
}
-
- return true;
}
bool AXTree::CreateNewChildVector(AXNode* node,
const std::vector<int32_t>& new_child_ids,
std::vector<AXNode*>* new_children,
AXTreeUpdateState* update_state) {
+ DCHECK(GetTreeUpdateInProgressState());
bool success = true;
for (size_t i = 0; i < new_child_ids.size(); ++i) {
int32_t child_id = new_child_ids[i];
@@ -1007,8 +1704,7 @@ bool AXTree::CreateNewChildVector(AXNode* node,
child->SetIndexInParent(i);
} else {
child = CreateNode(node, child_id, i, update_state);
- update_state->pending_nodes.insert(child);
- update_state->new_nodes.insert(child);
+ update_state->pending_nodes.insert(child->id());
}
new_children->push_back(child);
}
@@ -1037,8 +1733,8 @@ void AXTree::PopulateOrderedSetItems(const AXNode* ordered_set,
const AXNode* local_parent,
std::vector<const AXNode*>& items,
const AXNode& original_node) const {
- // ignored nodes are not a part of ordered sets.
- if (original_node.data().HasState(ax::mojom::State::kIgnored))
+ // Ignored nodes are not a part of ordered sets.
+ if (original_node.IsIgnored())
return;
// Stop searching current path if roles of local_parent and ordered set match.