| // Copyright 2013 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ | |
| #define UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <ctime> | |
| #include <map> | |
| #include <memory> | |
| #include <ostream> | |
| #include <set> | |
| #include <vector> | |
| #include "base/debug/crash_logging.h" | |
| #include "base/logging.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/memory/raw_ptr_exclusion.h" | |
| #include "base/notreached.h" | |
| #include "base/time/time.h" | |
| #include "base/timer/elapsed_timer.h" | |
| #include "ui/accessibility/ax_common.h" | |
| #include "ui/accessibility/ax_error_types.h" | |
| #include "ui/accessibility/ax_export.h" | |
| #include "ui/accessibility/ax_tree_data.h" | |
| #include "ui/accessibility/ax_tree_source.h" | |
| #include "ui/accessibility/ax_tree_update.h" | |
| namespace ui { | |
| struct ClientTreeNode; | |
| // AXTreeSerializer is a helper class that serializes incremental | |
| // updates to an AXTreeSource as a AXTreeUpdateType struct. | |
| // These structs can be unserialized by a client object such as an | |
| // AXTree. An AXTreeSerializer keeps track of the tree of node ids that its | |
| // client is aware of so that it will never generate an AXTreeUpdateType that | |
| // results in an invalid tree. | |
| // | |
| // Every node in the source tree must have an id that's a unique positive | |
| // integer, the same node must not appear twice. | |
| // | |
| // Usage: | |
| // | |
| // You must call SerializeChanges() every time a node in the tree changes, | |
| // and send the generated AXTreeUpdateType to the client. Changes to the | |
| // AXTreeData, if any, are also automatically included in the AXTreeUpdateType. | |
| // | |
| // If a node is added, call SerializeChanges on its parent. | |
| // If a node is removed, call SerializeChanges on its parent. | |
| // If a whole new subtree is added, just call SerializeChanges on its root. | |
| // If the root of the tree changes, call SerializeChanges on the new root. | |
| // | |
| // AXTreeSerializer will avoid re-serializing nodes that do not change. | |
| // For example, if node 1 has children 2, 3, 4, 5 and then child 2 is | |
| // removed and a new child 6 is added, the AXTreeSerializer will only | |
| // update nodes 1 and 6 (and any children of node 6 recursively). It will | |
| // assume that nodes 3, 4, and 5 are not modified unless you explicitly | |
| // call SerializeChanges() on them. | |
| // | |
| // As long as the source tree has unique ids for every node and no loops, | |
| // and as long as every update is applied to the client tree, AXTreeSerializer | |
| // will continue to work. If the source tree makes a change but fails to | |
| // call SerializeChanges properly, the trees may get out of sync - but | |
| // because AXTreeSerializer always keeps track of what updates it's sent, | |
| // it will never send an invalid update and the client tree will not break, | |
| // it just may not contain all of the changes. | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| class AXTreeSerializer { | |
| public: | |
| explicit AXTreeSerializer( | |
| AXTreeSource<AXSourceNode, AXTreeDataType, AXNodeDataType>* tree, | |
| bool crash_on_error = true); | |
| ~AXTreeSerializer(); | |
| // Throw out the internal state that keeps track of the nodes the client | |
| // knows about. This has the effect that the next update will send the | |
| // entire tree over because it assumes the client knows nothing. | |
| void Reset(); | |
| // Sets the maximum number of nodes that will be serialized, or zero | |
| // for no maximum. This is not a hard maximum - once it hits or | |
| // exceeds this maximum it stops walking the children of nodes, but | |
| // it may exceed this value a bit in order to create a consistent | |
| // tree. | |
| void set_max_node_count(size_t max_node_count) { | |
| max_node_count_ = max_node_count; | |
| } | |
| // Sets the maximum amount of time to be spend serializing, or zero for | |
| // no maximum. This is not a hard maximum - once it hits or | |
| // exceeds this timeout it stops walking the children of nodes, but | |
| // it may exceed this value a bit in order to create a consistent | |
| // tree. This is only intended to be used for one-time tree snapshots. | |
| void set_timeout(base::TimeDelta timeout) { timeout_ = timeout; } | |
| // Serialize all changes to |node| and append them to |out_update|. | |
| // Returns true on success. On failure, returns false and calls Reset(); | |
| // this only happens when the source tree has a problem like duplicate | |
| // ids or changing during serialization. | |
| bool SerializeChanges( | |
| AXSourceNode node, | |
| AXTreeUpdateType out_update, | |
| std::set<AXSerializationErrorFlag>* out_error = nullptr); | |
| // Invalidate the subtree rooted at this node, ensuring that the entire | |
| // subtree is re-serialized the next time any of those nodes end up | |
| // being serialized. | |
| void MarkSubtreeDirty(AXNodeID id); | |
| // Invalidate a single node, ensuring that it is reserialized. | |
| void MarkNodeDirty(AXNodeID id); | |
| // Return whether or not this node is in the client tree. If you call | |
| // this immediately after serializing, this indicates whether a given | |
| // node is in the set of nodes that the client (the recipient of | |
| // the AXTreeUpdates) is aware of. | |
| // | |
| // For example, you could use this to determine if a given node is | |
| // reachable. If one of its ancestors is hidden and it was pruned | |
| // from the accessibility tree, this would return false. | |
| bool IsInClientTree(AXSourceNode node); | |
| // Only for unit testing. Normally this class relies on getting a call | |
| // to SerializeChanges() every time the source tree changes. For unit | |
| // testing, it's convenient to create a static AXTree for the initial | |
| // state and then call ChangeTreeSourceForTesting and then SerializeChanges | |
| // to simulate the changes you'd get if a tree changed from the initial | |
| // state to the second tree's state. | |
| void ChangeTreeSourceForTesting( | |
| AXTreeSource<AXSourceNode, AXTreeDataType, AXNodeDataType>* new_tree); | |
| // Returns the number of nodes in the client tree. After a serialization | |
| // operation this should be an accurate representation of the tree source | |
| // as explored by the serializer. | |
| size_t ClientTreeNodeCount() const; | |
| #if AX_FAIL_FAST_BUILD() | |
| std::vector<AXNodeID> ClientTreeNodeIds() const; | |
| AXSourceNode ParentOf(AXNodeID id); | |
| #endif | |
| private: | |
| // Return the least common ancestor of a node in the source tree | |
| // and a node in the client tree, or nullptr if there is no such node. | |
| // The least common ancestor is the closest ancestor to |node| (which | |
| // may be |node| itself) that's in both the source tree and client tree, | |
| // and for which both the source and client tree agree on their ancestor | |
| // chain up to the root. | |
| // | |
| // Example 1: | |
| // | |
| // Client Tree Source tree | | |
| // 1 1 | | |
| // / \ / \ | | |
| // 2 3 2 4 | | |
| // | |
| // LCA(source node 2, client node 2) is node 2. | |
| // LCA(source node 3, client node 4) is node 1. | |
| // | |
| // Example 2: | |
| // | |
| // Client Tree Source tree | | |
| // 1 1 | | |
| // / \ / \ | | |
| // 2 3 2 3 | | |
| // / \ / / | | |
| // 4 7 8 4 | | |
| // / \ / \ | | |
| // 5 6 5 6 | | |
| // | |
| // LCA(source node 8, client node 7) is node 2. | |
| // LCA(source node 5, client node 5) is node 1. | |
| // It's not node 5, because the two trees disagree on the parent of | |
| // node 4, so the LCA is the first ancestor both trees agree on. | |
| AXSourceNode LeastCommonAncestor(AXSourceNode node, | |
| ClientTreeNode* client_node); | |
| // Return the least common ancestor of |node| that's in the client tree. | |
| // This just walks up the ancestors of |node| until it finds a node that's | |
| // also in the client tree and not inside a dirty subtree, and then calls | |
| // LeastCommonAncestor on the source node and client node. | |
| AXSourceNode LeastCommonAncestor(AXSourceNode node); | |
| // Look for reparenting in the subtree, and, if found, update the LCA with | |
| // both the old and new parent, and clear its subtree. | |
| // Return true if there was reparenting. | |
| bool ComputeReparentingLCA(AXSourceNode* lca); | |
| // Walk the subtree rooted at |node| and return true if any nodes that | |
| // would be updated are being reparented. If so, update |out_lca| to point | |
| // to the least common ancestor of the previous LCA and the previous | |
| // parent of the node being reparented. | |
| bool AnyDescendantWasReparented(AXSourceNode node, | |
| AXSourceNode* out_lca); | |
| ClientTreeNode* ClientTreeNodeById(AXNodeID id); | |
| void CreateClientRoot(AXSourceNode root); | |
| // Mark as dirty the subtree rooted at this node. | |
| void MarkClientSubtreeDirty(ClientTreeNode* client_node); | |
| // Delete all descendants of this node. | |
| void DeleteDescendants(ClientTreeNode* client_node); | |
| void DeleteDescendants(AXSourceNode node); | |
| // Delete the client subtree rooted at this node. | |
| void DeleteClientSubtree(ClientTreeNode* client_node); | |
| // Helper function, called recursively with each new node to serialize. | |
| bool SerializeChangedNodes( | |
| AXSourceNode node, | |
| AXTreeUpdateType out_update, | |
| std::set<AXSerializationErrorFlag>* out_error = nullptr); | |
| ClientTreeNode* GetClientTreeNodeParent(ClientTreeNode* obj); | |
| // The tree source. | |
| raw_ptr<AXTreeSource<AXSourceNode, AXTreeDataType, AXNodeDataType>, | |
| DanglingUntriaged> | |
| tree_; | |
| // The tree data most recently sent to the client. | |
| AXTreeData client_tree_data_; | |
| // Our representation of the client tree. | |
| raw_ptr<ClientTreeNode, DanglingUntriaged> client_root_ = nullptr; | |
| // A map from IDs to nodes in the client tree. | |
| std::map<AXNodeID, ClientTreeNode*> client_id_map_; | |
| // The maximum number of nodes to serialize in a given call to | |
| // SerializeChanges, or 0 if there's no maximum. | |
| size_t max_node_count_ = 0; | |
| // The maximum time to spend serializing before timing out, or 0 | |
| // if there's no maximum. | |
| base::TimeDelta timeout_; | |
| // The timer, which runs if there's a nonzero timeout and it hasn't | |
| // yet expired. Once the timeout elapses, the timer is deleted. | |
| std::unique_ptr<base::ElapsedTimer> timer_; | |
| // If Reset() Is called, stores the previous root of the entire tree. The | |
| // next serialization will set node_id_to_clear to this value ensure that the | |
| // previously serialized tree is also completely cleared on the | |
| // deserialization side. The id of tree_->GetRoot() cannot be changed because | |
| // the root may have changed since the previous serialization. | |
| AXNodeID client_root_id_to_clear_after_reset_ = kInvalidAXNodeID; | |
| // Whether to crash the process on serialization error or not. | |
| const bool crash_on_error_; | |
| }; | |
| // In order to keep track of what nodes the client knows about, we keep a | |
| // representation of the client tree - just IDs and parent/child | |
| // relationships, and a marker indicating whether it's been dirtied. | |
| struct AX_EXPORT ClientTreeNode final { | |
| ClientTreeNode(AXNodeID id, ClientTreeNode* parent); | |
| ~ClientTreeNode(); | |
| bool IsDirty() { return in_dirty_subtree || is_dirty; } | |
| const AXNodeID id; | |
| const raw_ptr<ClientTreeNode, DanglingUntriaged> parent; | |
| // Not a vector<raw_ptr> due to regressions in blink_perf.accessibility tests. | |
| RAW_PTR_EXCLUSION std::vector<ClientTreeNode*> children; | |
| bool ignored : 1; | |
| // Additional nodes that must be serialized. When a dirty subtree is reached, | |
| // the entire subtree will be added to the current serialization. | |
| // For this to occur, the root of the dirty subtree must be reached in | |
| // SerializedChanges(), which occurs when one of its nodes or an ancestor is | |
| // passed in. | |
| // TODO(accessibility) It is an error if there any dirty nodes remaining | |
| // after serialization is complete, and this could be turned into a CHECK. | |
| bool in_dirty_subtree : 1; | |
| // An individual node that is dirty, but its subtree may not be. | |
| bool is_dirty : 1; | |
| }; | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>:: | |
| AXTreeSerializer( | |
| AXTreeSource<AXSourceNode, AXTreeDataType, AXNodeDataType>* tree, | |
| bool crash_on_error) | |
| : tree_(tree), crash_on_error_(crash_on_error) {} | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::~AXTreeSerializer() { | |
| // Clear |tree_| to prevent any additional calls to the tree source | |
| // during teardown. | |
| // TODO(accessibility) How would that happen? | |
| tree_ = nullptr; | |
| // Free up any resources allocated on the heap that are stored with raw_ptr. | |
| Reset(); | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| void AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::Reset() { | |
| if (client_root_) { | |
| // This id must be set from the client tree root, and not from the tree | |
| // source root, so that if the root changes the correct subtree is still | |
| // cleared. | |
| client_root_id_to_clear_after_reset_ = client_root_->id; | |
| } | |
| client_tree_data_ = AXTreeData(); | |
| // Normally we use DeleteClientSubtree to remove nodes from the tree, | |
| // but Reset() needs to work even if the tree is in a broken state. | |
| // Instead, iterate over |client_id_map_| to ensure we clear all nodes and | |
| // start from scratch. | |
| for (auto&& item : client_id_map_) | |
| delete item.second; | |
| client_id_map_.clear(); | |
| client_root_ = nullptr; | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| void AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>:: | |
| ChangeTreeSourceForTesting( | |
| AXTreeSource<AXSourceNode, AXTreeDataType, AXNodeDataType>* new_tree) { | |
| tree_ = new_tree; | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| size_t AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::ClientTreeNodeCount() const { | |
| return client_id_map_.size(); | |
| } | |
| #if AX_FAIL_FAST_BUILD() | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| std::vector<AXNodeID> AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::ClientTreeNodeIds() | |
| const { | |
| std::vector<AXNodeID> keys; | |
| std::transform( | |
| client_id_map_.begin(), client_id_map_.end(), std::back_inserter(keys), | |
| [](std::pair<AXNodeID, ClientTreeNode*> item) { return item.first; }); | |
| return keys; | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| AXSourceNode AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::ParentOf(AXNodeID id) { | |
| ClientTreeNode* node = ClientTreeNodeById(id); | |
| if (!node || !node->parent) { | |
| return nullptr; | |
| } | |
| return tree_->GetFromId(node->parent->id); | |
| } | |
| #endif // AX_FAIL_FAST_BUILD() | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| AXSourceNode AXTreeSerializer< | |
| AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::LeastCommonAncestor(AXSourceNode node, | |
| ClientTreeNode* client_node) { | |
| if (!node || client_node == nullptr) { | |
| return tree_->GetNull(); | |
| } | |
| AXSourceNodeVectorType ancestors; | |
| while (node) { | |
| ancestors.push_back(node); | |
| node = tree_->GetParent(node); | |
| } | |
| std::vector<ClientTreeNode*> client_ancestors; | |
| while (client_node) { | |
| client_ancestors.push_back(client_node); | |
| client_node = GetClientTreeNodeParent(client_node); | |
| } | |
| // Start at the root. Keep going until the source ancestor chain and | |
| // client ancestor chain disagree. The last node before they disagree | |
| // is the LCA. | |
| AXSourceNode lca = tree_->GetNull(); | |
| for (size_t source_index = ancestors.size(), | |
| client_index = client_ancestors.size(); | |
| source_index > 0 && client_index > 0; --source_index, --client_index) { | |
| if (tree_->GetId(ancestors[(unsigned int)(source_index - 1)]) != | |
| client_ancestors[client_index - 1]->id) { | |
| // The passed-in |node| must be serialized. To ensure this, mark the | |
| // downward path from the new LCA to |node| as dirty. Use the source tree | |
| // as opposed to the client tree, because the serializer traverses that. | |
| for (unsigned int dirty_index = 0; dirty_index < source_index; | |
| ++dirty_index) { | |
| AXNodeID source_id = tree_->GetId(ancestors[dirty_index]); | |
| if (ClientTreeNode* node_mark_dirty = ClientTreeNodeById(source_id)) { | |
| node_mark_dirty->is_dirty = true; | |
| } | |
| } | |
| return lca; | |
| } | |
| lca = ancestors[(unsigned int)(source_index - 1)]; | |
| } | |
| return lca; | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| AXSourceNode | |
| AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::LeastCommonAncestor(AXSourceNode node) { | |
| // Walk up the tree until the source node's id also exists in the | |
| // client tree, whose parent is not dirty, then call LeastCommonAncestor | |
| // on those two nodes. | |
| // | |
| // Note that it's okay if |client_node| is dirty - the LCA can be the | |
| // root of a dirty subtree, since we're going to serialize the | |
| // LCA. But it's not okay if |client_node->parent| is dirty - that means | |
| // that we're inside of a dirty subtree that all needs to be re-serialized, so | |
| // the LCA should be higher. | |
| ClientTreeNode* client_node = ClientTreeNodeById(tree_->GetId(node)); | |
| while (node) { | |
| if (client_node) { | |
| ClientTreeNode* parent = GetClientTreeNodeParent(client_node); | |
| if (!parent || !parent->in_dirty_subtree) { | |
| break; | |
| } | |
| } | |
| node = tree_->GetParent(node); | |
| if (node) { | |
| client_node = ClientTreeNodeById(tree_->GetId(node)); | |
| } | |
| } | |
| return LeastCommonAncestor(node, client_node); | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| bool AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::ComputeReparentingLCA(AXSourceNode* | |
| lca) { | |
| // Look for reparenting under the lca subtree, and, if found, update lca using | |
| // both the old and new parent, and clear its subtree. | |
| // Returns true if there was reparenting, as an indication to clear the tree | |
| // rooted at the lca on deserialization. | |
| CHECK(lca); | |
| bool had_reparenting = false; | |
| while (*lca && AnyDescendantWasReparented(*lca, /*out_lca*/ lca)) { | |
| // LCA has been moved up in the tree to the common ancestor with a | |
| // reparented descendant. | |
| DeleteDescendants(*lca); | |
| had_reparenting = true; | |
| } | |
| if (*lca) { | |
| return had_reparenting; | |
| } | |
| // There was no LCA: clear everything and treat it as if the LCA was the root | |
| // of the tree so that everything is cleared on the receiving end. | |
| Reset(); | |
| return true; | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| bool AXTreeSerializer< | |
| AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::AnyDescendantWasReparented(AXSourceNode node, | |
| AXSourceNode* out_lca) { | |
| bool result = false; | |
| int id = tree_->GetId(node); | |
| tree_->CacheChildrenIfNeeded(node); | |
| auto num_children = tree_->GetChildCount(node); | |
| for (size_t i = 0; i < num_children; ++i) { | |
| AXSourceNode child = tree_->ChildAt(node, i); | |
| CHECK(child); | |
| int child_id = tree_->GetId(child); | |
| ClientTreeNode* client_child = ClientTreeNodeById(child_id); | |
| if (client_child) { | |
| ClientTreeNode* parent = client_child->parent; | |
| if (!parent) { | |
| // If the client child has no parent, it must have been the | |
| // previous root node, so there is no LCA and we can exit early. | |
| *out_lca = tree_->GetNull(); | |
| tree_->ClearChildCache(node); | |
| return true; | |
| } else if (parent->id != id) { | |
| // If the client child's parent is not this node, update the LCA | |
| // and return true (reparenting was found). | |
| *out_lca = LeastCommonAncestor(*out_lca, client_child); | |
| result = true; | |
| continue; | |
| } else if (!client_child->IsDirty()) { | |
| // This child is already in the client tree and not dirty, we won't | |
| // recursively serialize it so we don't need to check this | |
| // subtree recursively for reparenting. | |
| // However, if the child is or was ignored, the children may now be | |
| // considered as reparented, so continue recursion in that case. | |
| if (!client_child->ignored && !tree_->IsIgnored(child)) | |
| continue; | |
| } | |
| } | |
| // This is a new child or reparented child, check it recursively. | |
| if (AnyDescendantWasReparented(child, out_lca)) | |
| result = true; | |
| } | |
| tree_->ClearChildCache(node); | |
| return result; | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| void AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::CreateClientRoot(AXSourceNode root) { | |
| // A new client root must be created for the first serialization after | |
| // construction or a Reset(), otherwise serialization cannot succeed. | |
| client_root_ = new ClientTreeNode(tree_->GetId(root), nullptr); | |
| client_id_map_[client_root_->id] = client_root_; | |
| CHECK(!tree_->GetParent(root)) | |
| << "A root should never have a parent, but " | |
| "the tree source thinks there is one:" | |
| << "\n* Root: " << tree_->GetDebugString(root) << "\n* Parent of root: " | |
| << tree_->GetDebugString(tree_->GetParent(root)); | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| ClientTreeNode* | |
| AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::ClientTreeNodeById(AXNodeID id) { | |
| std::map<AXNodeID, ClientTreeNode*>::iterator iter = client_id_map_.find(id); | |
| if (iter != client_id_map_.end()) | |
| return iter->second; | |
| return nullptr; | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| ClientTreeNode* | |
| AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::GetClientTreeNodeParent(ClientTreeNode* obj) { | |
| ClientTreeNode* parent = obj->parent; | |
| if (!parent) | |
| return nullptr; | |
| if (!ClientTreeNodeById(parent->id)) { | |
| std::ostringstream error; | |
| error << "Child: " << tree_->GetDebugString(tree_->EnsureGetFromId(obj->id)) | |
| << "\nParent: " | |
| << tree_->GetDebugString(tree_->EnsureGetFromId(parent->id)); | |
| static auto* missing_parent_err = base::debug::AllocateCrashKeyString( | |
| "ax_ts_missing_parent_err", base::debug::CrashKeySize::Size256); | |
| base::debug::SetCrashKeyString(missing_parent_err, | |
| error.str().substr(0, 230)); | |
| if (crash_on_error_) { | |
| NOTREACHED() << error.str(); | |
| } else { | |
| LOG(ERROR) << error.str(); | |
| // Different from other errors, not calling Reset() here to avoid breaking | |
| // the internal state of this class. | |
| } | |
| } | |
| return parent; | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| bool AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>:: | |
| SerializeChanges(AXSourceNode node, | |
| AXTreeUpdateType out_update, | |
| std::set<AXSerializationErrorFlag>* out_error) { | |
| if (!timeout_.is_zero()) | |
| timer_ = std::make_unique<base::ElapsedTimer>(); | |
| CHECK(tree_->GetId(node) != kInvalidAXNodeID); | |
| // If the root changed: reset the tree, clear the root, and start over. | |
| if (client_root_ && client_root_->id != tree_->GetId(tree_->GetRoot())) { | |
| Reset(); | |
| } | |
| // If we had a reset, ensure that the old tree is cleared before the client | |
| // unserializes this update. If we didn't do this, there's a chance that | |
| // treating this update as an incremental update could result in some | |
| // reparenting. | |
| if (client_root_id_to_clear_after_reset_ != kInvalidAXNodeID) { | |
| out_update->node_id_to_clear = client_root_id_to_clear_after_reset_; | |
| client_root_id_to_clear_after_reset_ = 0; | |
| } | |
| // The LCA will be the least common ancestor for the serialization and used | |
| // as the root of the AXTreeUpdate. | |
| AXSourceNode lca = nullptr; | |
| // This computes the least common ancestor that includes the old | |
| // and new parents of any nodes that have been reparented, and clears the | |
| // whole client subtree of that LCA if necessary. If we do end up clearing | |
| // any client nodes, keep looping because we have to search for more | |
| // nodes that may have been reparented from this new LCA. | |
| if (client_root_) { | |
| // Not the first serialization: compute the LCA. | |
| // Although LeastCommonAncestor() is passed a single node, it actually uses | |
| // uses the node id to get an LCA from two sets of ancestors of the node: | |
| // 1) ancestors the client tree, which contains the last serialized tree | |
| // 2) ancestors in the source tree, which contains the current tree | |
| // Because of this, if `node` has been moved, the LCA will be an ancestor | |
| // of both its old and new positions in the tree. | |
| // In addition, the LCA may be moved to a higher ancestor in order to | |
| // prevent it from being on node with a dirty parent or parent that hasn't | |
| // yet been serialized. This ensures that the serialization is not missing | |
| // dirty or new nodes. | |
| lca = LeastCommonAncestor(node); | |
| // Look for reparenting in descendants of the subtree, and, if found, update | |
| // the LCA with both the old and new parents of that descendant. | |
| if (ComputeReparentingLCA(&lca)) { | |
| // Reparenting was found in the subtree. Inform the deserializer | |
| // to delete the subtree before receiving it, so that the final result | |
| // does not contain two subtrees with the same node. The LCA now contains | |
| // subtrees for both the old and new parent. | |
| if (!lca) { | |
| // TODO(442619489) Fix the root cause of this issue, replacing the | |
| // DUMP_WILL_BE_NOTREACHED with a CHECK once we have ensured this | |
| // can no longer occur. | |
| DUMP_WILL_BE_NOTREACHED() | |
| << "We should not have a null LCA when ComputeReparentingLCA " | |
| "returns with 'true'."; | |
| } else { | |
| out_update->node_id_to_clear = tree_->GetId(lca); | |
| } | |
| } | |
| } else { | |
| // First serialization for this tree, after a changed root, or after a | |
| // Reset():use the root for the LCA and create the client root. | |
| lca = tree_->GetRoot(); | |
| CreateClientRoot(lca); | |
| } | |
| CHECK(tree_->GetId(lca) != kInvalidAXNodeID); | |
| if (!SerializeChangedNodes(lca, out_update, out_error)) { | |
| return false; | |
| } | |
| // Send the tree data if it's changed since the last update, or if | |
| // out_update->has_tree_data is already set to true. | |
| // Do this last, so that selection retrieval will cause recomputation of | |
| // node inclusion before the the new tree structure has been updated in a | |
| // top-down matter via SerializeChangedNodes(). | |
| AXTreeData new_tree_data; | |
| if (tree_->GetTreeData(&new_tree_data) && | |
| (out_update->has_tree_data || new_tree_data != client_tree_data_)) { | |
| out_update->has_tree_data = true; | |
| out_update->tree_data = new_tree_data; | |
| client_tree_data_ = std::move(new_tree_data); | |
| } | |
| return true; | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| void AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::MarkNodeDirty(AXNodeID id) { | |
| if (ClientTreeNode* client_node = ClientTreeNodeById(id)) { | |
| client_node->is_dirty = true; | |
| } | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| void AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::MarkSubtreeDirty(AXNodeID id) { | |
| if (ClientTreeNode* client_node = ClientTreeNodeById(id)) { | |
| MarkClientSubtreeDirty(client_node); | |
| } | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| bool AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::IsInClientTree(AXSourceNode node) { | |
| return ClientTreeNodeById(tree_->GetId(node)); | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| void AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::MarkClientSubtreeDirty(ClientTreeNode* | |
| client_node) { | |
| // Return early if already marked dirty, in order to avoid duplicate work in | |
| // subtree, as the only method that marks nodes dirty is this one. | |
| if (client_node->in_dirty_subtree) { | |
| return; | |
| } | |
| client_node->in_dirty_subtree = true; | |
| for (ClientTreeNode* child : client_node->children) { | |
| MarkClientSubtreeDirty(child); | |
| } | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| void AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::DeleteClientSubtree(ClientTreeNode* | |
| client_node) { | |
| if (client_node == client_root_) { | |
| // Do not try to reuse a bad root later. | |
| // A heuristic for this condition rather than an explicit Reset() from a | |
| // caller makes it difficult to debug whether extra resets / lost virtual | |
| // buffer positions are occurring because of this code. Therefore, a CHECK | |
| // has been added in order to debug if or when this condition may occur. | |
| CHECK(!crash_on_error_) | |
| << "Attempt to delete entire client subtree, including the root."; | |
| } else { | |
| DeleteDescendants(client_node); | |
| client_id_map_.erase(client_node->id); | |
| delete client_node; | |
| } | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| void AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::DeleteDescendants(ClientTreeNode* | |
| client_node) { | |
| for (size_t i = 0; i < client_node->children.size(); ++i) | |
| DeleteClientSubtree(client_node->children[i]); | |
| client_node->children.clear(); | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| void AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>::DeleteDescendants(AXSourceNode node) { | |
| ClientTreeNode* client_node = ClientTreeNodeById(tree_->GetId(node)); | |
| CHECK(client_node); | |
| DeleteDescendants(client_node); | |
| } | |
| template <typename AXSourceNode, | |
| typename AXSourceNodeVectorType, | |
| typename AXTreeUpdateType, | |
| typename AXTreeDataType, | |
| typename AXNodeDataType> | |
| bool AXTreeSerializer<AXSourceNode, | |
| AXSourceNodeVectorType, | |
| AXTreeUpdateType, | |
| AXTreeDataType, | |
| AXNodeDataType>:: | |
| SerializeChangedNodes(AXSourceNode node, | |
| AXTreeUpdateType out_update, | |
| std::set<AXSerializationErrorFlag>* out_error) { | |
| // This method has three responsibilities: | |
| // 1. Serialize |node| into an AXNodeData, and append it to | |
| // the AXTreeUpdateType to be sent to the client. | |
| // 2. Determine if |node| has any new children that the client doesn't | |
| // know about yet, and call SerializeChangedNodes recursively on those. | |
| // 3. Update our internal data structure that keeps track of what nodes | |
| // the client knows about. | |
| // First, find the ClientTreeNode for this id in our data structure where | |
| // we keep track of what accessibility objects the client already knows | |
| // about. | |
| // If we don't find it, then the intention may be to use it as the | |
| // new root of the accessibility tree. A heuristic for this condition rather | |
| // than an explicit Reset() from a caller makes it difficult to debug whether | |
| // extra resets / lost virtual buffer positions are occurring because of this | |
| // code. Therefore, a CHECK has been added in order to debug if or when this | |
| // condition may occur. | |
| int id = tree_->GetId(node); | |
| ClientTreeNode* client_node = ClientTreeNodeById(id); | |
| CHECK(client_node); | |
| // We're about to serialize it, so clear its dirty states. | |
| client_node->in_dirty_subtree = false; | |
| client_node->is_dirty = false; | |
| client_node->ignored = tree_->IsIgnored(node); | |
| // Terminate early if a maximum number of nodes is reached. | |
| // the output tree is still consistent). | |
| bool should_terminate_early = false; | |
| if (max_node_count_ > 0 && out_update->nodes.size() >= max_node_count_) { | |
| should_terminate_early = true; | |
| if (out_error) { | |
| (*out_error).insert(AXSerializationErrorFlag::kMaxNodesReached); | |
| } | |
| } | |
| // Also terminate early if a timeout is reached. | |
| if (!timeout_.is_zero()) { | |
| if (timer_ && timer_->Elapsed() >= timeout_) { | |
| // Terminate early and delete the timer so that we don't have to | |
| // keep checking if we timed out. | |
| should_terminate_early = true; | |
| if (out_error) { | |
| (*out_error).insert(AXSerializationErrorFlag::kTimeoutReached); | |
| } | |
| timer_.reset(); | |
| } else if (!timer_) { | |
| // Already timed out; keep terminating early until the serialization | |
| // is done. | |
| should_terminate_early = true; | |
| } | |
| } | |
| // Iterate over the ids of the children of |node|. | |
| // Create a set of the child ids so we can quickly look | |
| // up which children are new and which ones were there before. | |
| // If we've hit the maximum number of serialized nodes, pretend | |
| // this node has no children but keep going so that we get | |
| // consistent results. | |
| std::set<AXNodeID> new_ignored_ids; | |
| std::set<AXNodeID> new_child_ids; | |
| size_t num_children = 0; | |
| if (!should_terminate_early) { | |
| tree_->CacheChildrenIfNeeded(node); | |
| num_children = tree_->GetChildCount(node); | |
| } | |
| for (size_t i = 0; i < num_children; ++i) { | |
| AXSourceNode child = tree_->ChildAt(node, i); | |
| CHECK(child); | |
| int new_child_id = tree_->GetId(child); | |
| new_child_ids.insert(new_child_id); | |
| if (tree_->IsIgnored(child)) | |
| new_ignored_ids.insert(new_child_id); | |
| // There shouldn't be any reparenting because we've already handled it | |
| // above. If this happens, reset and return an error. | |
| ClientTreeNode* client_child = ClientTreeNodeById(new_child_id); | |
| if (client_child && GetClientTreeNodeParent(client_child) != client_node) { | |
| // This condition leads to performance problems. It will | |
| // also reset virtual buffers, causing users to lose their place. | |
| std::ostringstream error; | |
| error << "Passed-in parent: " | |
| << tree_->GetDebugString(tree_->EnsureGetFromId(client_node->id)) | |
| << "\nChild: " << tree_->GetDebugString(child) | |
| << "\nChild's parent: " | |
| << tree_->GetDebugString( | |
| tree_->EnsureGetFromId(client_child->parent->id)); | |
| static auto* reparent_err = base::debug::AllocateCrashKeyString( | |
| "ax_ts_reparent_err", base::debug::CrashKeySize::Size256); | |
| base::debug::SetCrashKeyString(reparent_err, error.str().substr(0, 230)); | |
| if (crash_on_error_) { | |
| NOTREACHED() << error.str(); | |
| } else { | |
| LOG(ERROR) << error.str(); | |
| Reset(); | |
| } | |
| tree_->ClearChildCache(node); | |
| return false; | |
| } | |
| } | |
| // Go through the old children and delete subtrees for child | |
| // ids that are no longer present, and create a map from | |
| // id to ClientTreeNode for the rest. It's important to delete | |
| // first in a separate pass so that nodes that are reparented | |
| // don't end up children of two different parents in the middle | |
| // of an update, which can lead to a double-free. | |
| std::map<AXNodeID, ClientTreeNode*> client_child_id_map; | |
| std::vector<ClientTreeNode*> old_children; | |
| old_children.swap(client_node->children); | |
| for (size_t i = 0; i < old_children.size(); ++i) { | |
| ClientTreeNode* old_child = old_children[i]; | |
| int old_child_id = old_child->id; | |
| if (new_child_ids.find(old_child_id) == new_child_ids.end()) { | |
| DeleteClientSubtree(old_child); | |
| } else { | |
| client_child_id_map[old_child_id] = old_child; | |
| } | |
| } | |
| // Serialize this node. This fills in all of the fields in | |
| // AXNodeData except child_ids, which we handle below. | |
| const size_t serialized_node_index = out_update->nodes.size(); | |
| out_update->nodes.emplace_back(); | |
| { | |
| // Take the address of an element in a vector only within a limited | |
| // scope because otherwise the pointer can become invalid if the | |
| // vector is resized. | |
| AXNodeData* serialized_node = &out_update->nodes.back(); | |
| tree_->SerializeNode(node, serialized_node); | |
| if (serialized_node->id == client_root_->id) { | |
| out_update->root_id = serialized_node->id; | |
| CHECK(!client_root_->parent) << "The root cannot have a parent:"; | |
| // << "\n* Root: " | |
| // << tree_->GetDebugString(tree_->GetFromId(out_update->root_id)) | |
| // << "\n* Root's parent: " | |
| // << tree_->GetDebugString(tree_->GetFromId(client_root_->parent->id)); | |
| } else { | |
| CHECK(serialized_node->role != ax::mojom::Role::kRootWebArea) | |
| << "A kRootWebArea role was used on an object that is not the root: " | |
| << "\n* Actual root: " << tree_->GetDebugString(tree_->GetRoot()) | |
| << "\n* Illegal node with root web area role: " | |
| << tree_->GetDebugString(tree_->EnsureGetFromId(serialized_node->id)) | |
| << "\n* Parent of illegal node: " | |
| << (client_node->parent | |
| ? tree_->GetDebugString( | |
| tree_->EnsureGetFromId(client_node->parent->id)) | |
| : ""); | |
| } | |
| } | |
| // Iterate over the children, serialize them, and update the ClientTreeNode | |
| // data structure to reflect the new tree. | |
| std::vector<AXNodeID> actual_serialized_node_child_ids; | |
| client_node->children.reserve(num_children); | |
| for (size_t i = 0; i < num_children; ++i) { | |
| AXSourceNode child = tree_->ChildAt(node, i); | |
| CHECK(child); | |
| int child_id = tree_->GetId(child); | |
| // Skip if the same child is included more than once. | |
| if (new_child_ids.find(child_id) == new_child_ids.end()) | |
| continue; | |
| new_child_ids.erase(child_id); | |
| actual_serialized_node_child_ids.push_back(child_id); | |
| ClientTreeNode* reused_child = nullptr; | |
| if (client_child_id_map.find(child_id) != client_child_id_map.end()) | |
| reused_child = ClientTreeNodeById(child_id); | |
| if (reused_child) { | |
| client_node->children.push_back(reused_child); | |
| const bool ignored_state_changed = | |
| reused_child->ignored != | |
| (new_ignored_ids.find(reused_child->id) != new_ignored_ids.end()); | |
| // Re-serialize it if the child is marked as dirty, otherwise | |
| // we don't have to because the client already has it. | |
| if (reused_child->IsDirty() || ignored_state_changed) { | |
| if (!SerializeChangedNodes(child, out_update, out_error)) { | |
| tree_->ClearChildCache(node); | |
| return false; | |
| } | |
| } | |
| } else { | |
| ClientTreeNode* new_child = new ClientTreeNode(child_id, client_node); | |
| new_child->ignored = tree_->IsIgnored(child); | |
| new_child->in_dirty_subtree = false; | |
| new_child->is_dirty = false; | |
| client_node->children.push_back(new_child); | |
| if (ClientTreeNodeById(child_id)) { | |
| // TODO(accessibility) Remove all cases where this occurs and re-add | |
| // This condition leads to performance problems. It will | |
| // also reset virtual buffers, causing users to lose their place. | |
| std::ostringstream error; | |
| error << "Child id " << child_id << " already in map." | |
| << "\nChild: " | |
| << tree_->GetDebugString(tree_->EnsureGetFromId(child_id)) | |
| << "\nWanted for parent " << tree_->GetDebugString(node) | |
| << "\nAlready had parent " | |
| << tree_->GetDebugString(tree_->EnsureGetFromId( | |
| ClientTreeNodeById(child_id)->parent->id)); | |
| static auto* dupe_id_err = base::debug::AllocateCrashKeyString( | |
| "ax_ts_dupe_id_err", base::debug::CrashKeySize::Size256); | |
| base::debug::SetCrashKeyString(dupe_id_err, error.str().substr(0, 230)); | |
| if (crash_on_error_) { | |
| NOTREACHED() << error.str(); | |
| } else { | |
| LOG(ERROR) << error.str(); | |
| Reset(); | |
| } | |
| tree_->ClearChildCache(node); | |
| return false; | |
| } | |
| client_id_map_[child_id] = new_child; | |
| if (!SerializeChangedNodes(child, out_update, out_error)) { | |
| tree_->ClearChildCache(node); | |
| return false; | |
| } | |
| } | |
| } | |
| tree_->ClearChildCache(node); | |
| // Finally, update the child ids of this node to reflect the actual child | |
| // ids that were valid during serialization. | |
| out_update->nodes[serialized_node_index].child_ids.swap( | |
| actual_serialized_node_child_ids); | |
| return true; | |
| } | |
| } // namespace ui | |
| #endif // UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ |