Commit 5a7b3d98 authored by dtseng's avatar dtseng Committed by Commit bot

Track all changed nodes during an update

While deserializing, make a note of all changed nodes via id before performing any tree changes/removals.

While removing (non-atomically), reference the changed list to ensure we notify delegates of nodes that will actually be deleted and not just deleted then re-created as part of bookkeeping to enforce the "non-reparenting" invariant inside of AXTree::CreateChildVector.

BUG=642799
TEST=for each of the reproductions in the bug, ensure the automation api (as a client) does not destroy then re-create js objects; rather, ensure it re-uses them for reparented trees.

Review-Url: https://codereview.chromium.org/2080573003
Cr-Commit-Position: refs/heads/master@{#417207}
parent 047ac367
...@@ -1170,6 +1170,18 @@ void AutomationInternalCustomBindings::OnSubtreeWillBeDeleted( ...@@ -1170,6 +1170,18 @@ void AutomationInternalCustomBindings::OnSubtreeWillBeDeleted(
// be needed for something. // be needed for something.
} }
void AutomationInternalCustomBindings::OnNodeWillBeReparented(
ui::AXTree* tree,
ui::AXNode* node) {
// Don't do anything here since the node will soon go away and be re-created.
}
void AutomationInternalCustomBindings::OnSubtreeWillBeReparented(
ui::AXTree* tree,
ui::AXNode* node) {
// Don't do anything here since the node will soon go away and be re-created.
}
void AutomationInternalCustomBindings::OnNodeCreated(ui::AXTree* tree, void AutomationInternalCustomBindings::OnNodeCreated(ui::AXTree* tree,
ui::AXNode* node) { ui::AXNode* node) {
// Not needed, this is called in the middle of an update so it's not // Not needed, this is called in the middle of an update so it's not
...@@ -1177,6 +1189,13 @@ void AutomationInternalCustomBindings::OnNodeCreated(ui::AXTree* tree, ...@@ -1177,6 +1189,13 @@ void AutomationInternalCustomBindings::OnNodeCreated(ui::AXTree* tree,
// OnAtomicUpdateFinished instead. // OnAtomicUpdateFinished instead.
} }
void AutomationInternalCustomBindings::OnNodeReparented(ui::AXTree* tree,
ui::AXNode* node) {
// Not needed, this is called in the middle of an update so it's not
// safe to trigger JS from here. Wait for the notification in
// OnAtomicUpdateFinished instead.
}
void AutomationInternalCustomBindings::OnNodeChanged(ui::AXTree* tree, void AutomationInternalCustomBindings::OnNodeChanged(ui::AXTree* tree,
ui::AXNode* node) { ui::AXNode* node) {
// Not needed, this is called in the middle of an update so it's not // Not needed, this is called in the middle of an update so it's not
......
...@@ -159,7 +159,10 @@ class AutomationInternalCustomBindings : public ObjectBackedNativeHandler, ...@@ -159,7 +159,10 @@ class AutomationInternalCustomBindings : public ObjectBackedNativeHandler,
void OnTreeDataChanged(ui::AXTree* tree) override; void OnTreeDataChanged(ui::AXTree* tree) override;
void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
void OnNodeWillBeReparented(ui::AXTree* tree, ui::AXNode* node) override;
void OnSubtreeWillBeReparented(ui::AXTree* tree, ui::AXNode* node) override;
void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override; void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override;
void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override;
void OnNodeChanged(ui::AXTree* tree, ui::AXNode* node) override; void OnNodeChanged(ui::AXTree* tree, ui::AXNode* node) override;
void OnAtomicUpdateFinished(ui::AXTree* tree, void OnAtomicUpdateFinished(ui::AXTree* tree,
bool root_changed, bool root_changed,
......
...@@ -976,6 +976,24 @@ void BrowserAccessibilityManager::OnSubtreeWillBeDeleted(ui::AXTree* tree, ...@@ -976,6 +976,24 @@ void BrowserAccessibilityManager::OnSubtreeWillBeDeleted(ui::AXTree* tree,
obj->OnSubtreeWillBeDeleted(); obj->OnSubtreeWillBeDeleted();
} }
void BrowserAccessibilityManager::OnNodeWillBeReparented(ui::AXTree* tree,
ui::AXNode* node) {
// BrowserAccessibility should probably ask the tree source for the AXNode via
// an id rather than weakly holding a pointer to a AXNode that might have been
// destroyed under the hood and re-created later on. Treat this as a delete to
// make things work.
OnNodeWillBeDeleted(tree, node);
}
void BrowserAccessibilityManager::OnSubtreeWillBeReparented(ui::AXTree* tree,
ui::AXNode* node) {
// BrowserAccessibility should probably ask the tree source for the AXNode via
// an id rather than weakly holding a pointer to a AXNode that might have been
// destroyed under the hood and re-created later on. Treat this as a delete to
// make things work.
OnSubtreeWillBeDeleted(tree, node);
}
void BrowserAccessibilityManager::OnNodeCreated(ui::AXTree* tree, void BrowserAccessibilityManager::OnNodeCreated(ui::AXTree* tree,
ui::AXNode* node) { ui::AXNode* node) {
BrowserAccessibility* wrapper = factory_->Create(); BrowserAccessibility* wrapper = factory_->Create();
...@@ -984,6 +1002,15 @@ void BrowserAccessibilityManager::OnNodeCreated(ui::AXTree* tree, ...@@ -984,6 +1002,15 @@ void BrowserAccessibilityManager::OnNodeCreated(ui::AXTree* tree,
wrapper->OnDataChanged(); wrapper->OnDataChanged();
} }
void BrowserAccessibilityManager::OnNodeReparented(ui::AXTree* tree,
ui::AXNode* node) {
// BrowserAccessibility should probably ask the tree source for the AXNode via
// an id rather than weakly holding a pointer to a AXNode that might have been
// destroyed under the hood and re-created later on. Treat this as a create to
// make things work.
OnNodeCreated(tree, node);
}
void BrowserAccessibilityManager::OnNodeChanged(ui::AXTree* tree, void BrowserAccessibilityManager::OnNodeChanged(ui::AXTree* tree,
ui::AXNode* node) { ui::AXNode* node) {
DCHECK(node); DCHECK(node);
......
...@@ -354,7 +354,10 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeDelegate { ...@@ -354,7 +354,10 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeDelegate {
void OnTreeDataChanged(ui::AXTree* tree) override; void OnTreeDataChanged(ui::AXTree* tree) override;
void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
void OnNodeWillBeReparented(ui::AXTree* tree, ui::AXNode* node) override;
void OnSubtreeWillBeReparented(ui::AXTree* tree, ui::AXNode* node) override;
void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override; void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override;
void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override;
void OnNodeChanged(ui::AXTree* tree, ui::AXNode* node) override; void OnNodeChanged(ui::AXTree* tree, ui::AXNode* node) override;
void OnAtomicUpdateFinished( void OnAtomicUpdateFinished(
ui::AXTree* tree, ui::AXTree* tree,
......
...@@ -29,6 +29,15 @@ std::string TreeToStringHelper(AXNode* node, int indent) { ...@@ -29,6 +29,15 @@ std::string TreeToStringHelper(AXNode* node, int indent) {
// Intermediate state to keep track of during a tree update. // Intermediate state to keep track of during a tree update.
struct AXTreeUpdateState { struct AXTreeUpdateState {
AXTreeUpdateState() : new_root(nullptr) {} AXTreeUpdateState() : new_root(nullptr) {}
// Returns whether this update changes |node|.
bool HasChangedNode(const AXNode* node) {
return changed_node_ids.find(node->id()) != changed_node_ids.end();
}
// Returns whether this update removes |node|.
bool HasRemovedNode(const AXNode* node) {
return removed_node_ids.find(node->id()) != removed_node_ids.end();
}
// During an update, this keeps track of all nodes that have been // During an update, this keeps track of all nodes that have been
// implicitly referenced as part of this update, but haven't been // implicitly referenced as part of this update, but haven't been
...@@ -36,6 +45,12 @@ struct AXTreeUpdateState { ...@@ -36,6 +45,12 @@ struct AXTreeUpdateState {
// end of Unserialize. // end of Unserialize.
std::set<AXNode*> pending_nodes; std::set<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 delegates of removals or reparenting.
std::set<int> changed_node_ids;
// Keeps track of new nodes created during this update. // Keeps track of new nodes created during this update.
std::set<AXNode*> new_nodes; std::set<AXNode*> new_nodes;
...@@ -92,6 +107,10 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) { ...@@ -92,6 +107,10 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
AXTreeUpdateState update_state; AXTreeUpdateState update_state;
int32_t old_root_id = root_ ? root_->id() : 0; 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) if (update.has_tree_data)
UpdateData(update.tree_data); UpdateData(update.tree_data);
...@@ -146,9 +165,7 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) { ...@@ -146,9 +165,7 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
AXNode* node = GetFromId(update.nodes[i].id); AXNode* node = GetFromId(update.nodes[i].id);
bool is_new_node = new_nodes.find(node) != new_nodes.end(); bool is_new_node = new_nodes.find(node) != new_nodes.end();
bool is_reparented_node = bool is_reparented_node =
is_new_node && is_new_node && update_state.HasRemovedNode(node);
update_state.removed_node_ids.find(node->id()) !=
update_state.removed_node_ids.end();
AXTreeDelegate::ChangeType change = AXTreeDelegate::NODE_CHANGED; AXTreeDelegate::ChangeType change = AXTreeDelegate::NODE_CHANGED;
if (is_new_node) { if (is_new_node) {
...@@ -176,11 +193,17 @@ std::string AXTree::ToString() const { ...@@ -176,11 +193,17 @@ std::string AXTree::ToString() const {
AXNode* AXTree::CreateNode(AXNode* parent, AXNode* AXTree::CreateNode(AXNode* parent,
int32_t id, int32_t id,
int32_t index_in_parent) { int32_t index_in_parent,
AXTreeUpdateState* update_state) {
AXNode* new_node = new AXNode(parent, id, index_in_parent); AXNode* new_node = new AXNode(parent, id, index_in_parent);
id_map_[new_node->id()] = new_node; id_map_[new_node->id()] = new_node;
if (delegate_) if (delegate_) {
if (update_state->HasChangedNode(new_node) &&
!update_state->HasRemovedNode(new_node))
delegate_->OnNodeCreated(this, new_node); delegate_->OnNodeCreated(this, new_node);
else
delegate_->OnNodeReparented(this, new_node);
}
return new_node; return new_node;
} }
...@@ -208,7 +231,7 @@ bool AXTree::UpdateNode(const AXNodeData& src, ...@@ -208,7 +231,7 @@ bool AXTree::UpdateNode(const AXNodeData& src,
return false; return false;
} }
update_state->new_root = CreateNode(NULL, src.id, 0); update_state->new_root = CreateNode(NULL, src.id, 0, update_state);
node = update_state->new_root; node = update_state->new_root;
update_state->new_nodes.insert(node); update_state->new_nodes.insert(node);
node->SetData(src); node->SetData(src);
...@@ -247,15 +270,24 @@ bool AXTree::UpdateNode(const AXNodeData& src, ...@@ -247,15 +270,24 @@ bool AXTree::UpdateNode(const AXNodeData& src,
void AXTree::DestroySubtree(AXNode* node, void AXTree::DestroySubtree(AXNode* node,
AXTreeUpdateState* update_state) { AXTreeUpdateState* update_state) {
if (delegate_) DCHECK(update_state);
if (delegate_) {
if (!update_state->HasChangedNode(node))
delegate_->OnSubtreeWillBeDeleted(this, node); delegate_->OnSubtreeWillBeDeleted(this, node);
else
delegate_->OnSubtreeWillBeReparented(this, node);
}
DestroyNodeAndSubtree(node, update_state); DestroyNodeAndSubtree(node, update_state);
} }
void AXTree::DestroyNodeAndSubtree(AXNode* node, void AXTree::DestroyNodeAndSubtree(AXNode* node,
AXTreeUpdateState* update_state) { AXTreeUpdateState* update_state) {
if (delegate_) if (delegate_) {
if (!update_state || !update_state->HasChangedNode(node))
delegate_->OnNodeWillBeDeleted(this, node); delegate_->OnNodeWillBeDeleted(this, node);
else
delegate_->OnNodeWillBeReparented(this, node);
}
id_map_.erase(node->id()); id_map_.erase(node->id());
for (int i = 0; i < node->child_count(); ++i) for (int i = 0; i < node->child_count(); ++i)
DestroyNodeAndSubtree(node->ChildAtIndex(i), update_state); DestroyNodeAndSubtree(node->ChildAtIndex(i), update_state);
...@@ -316,7 +348,7 @@ bool AXTree::CreateNewChildVector(AXNode* node, ...@@ -316,7 +348,7 @@ bool AXTree::CreateNewChildVector(AXNode* node,
} }
child->SetIndexInParent(index_in_parent); child->SetIndexInParent(index_in_parent);
} else { } else {
child = CreateNode(node, child_id, index_in_parent); child = CreateNode(node, child_id, index_in_parent, update_state);
update_state->pending_nodes.insert(child); update_state->pending_nodes.insert(child);
update_state->new_nodes.insert(child); update_state->new_nodes.insert(child);
} }
......
...@@ -28,7 +28,10 @@ struct AXTreeUpdateState; ...@@ -28,7 +28,10 @@ struct AXTreeUpdateState;
// don't walk the parents and children at this time: // don't walk the parents and children at this time:
// OnNodeWillBeDeleted // OnNodeWillBeDeleted
// OnSubtreeWillBeDeleted // OnSubtreeWillBeDeleted
// OnNodeWillBeReparented
// OnSubtreeWillBeReparented
// OnNodeCreated // OnNodeCreated
// OnNodeReparented
// OnNodeChanged // OnNodeChanged
// //
// In addition, one additional notification is fired at the end of an // In addition, one additional notification is fired at the end of an
...@@ -59,10 +62,22 @@ class AX_EXPORT AXTreeDelegate { ...@@ -59,10 +62,22 @@ class AX_EXPORT AXTreeDelegate {
// invalid state! // invalid state!
virtual void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) = 0; virtual void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) = 0;
// Called just before a node is deleted for reparenting. See
// |OnNodeWillBeDeleted| for additional information.
virtual void OnNodeWillBeReparented(AXTree* tree, AXNode* node) = 0;
// Called just before a subtree is deleted for reparenting. See
// |OnSubtreeWillBeDeleted| for additional information.
virtual void OnSubtreeWillBeReparented(AXTree* tree, AXNode* node) = 0;
// Called immediately after a new node is created. The tree may be in // Called immediately after a new node is created. The tree may be in
// the middle of an update, don't walk the parents and children now. // the middle of an update, don't walk the parents and children now.
virtual void OnNodeCreated(AXTree* tree, AXNode* node) = 0; virtual void OnNodeCreated(AXTree* tree, AXNode* node) = 0;
// Called immediately after a node is reparented. The tree may be in the
// middle of an update, don't walk the parents and children now.
virtual void OnNodeReparented(AXTree* tree, AXNode* node) = 0;
// Called when a node changes its data or children. The tree may be in // Called when a node changes its data or children. The tree may be in
// the middle of an update, don't walk the parents and children now. // the middle of an update, don't walk the parents and children now.
virtual void OnNodeChanged(AXTree* tree, AXNode* node) = 0; virtual void OnNodeChanged(AXTree* tree, AXNode* node) = 0;
...@@ -131,7 +146,10 @@ class AX_EXPORT AXTree { ...@@ -131,7 +146,10 @@ class AX_EXPORT AXTree {
int size() { return static_cast<int>(id_map_.size()); } int size() { return static_cast<int>(id_map_.size()); }
private: private:
AXNode* CreateNode(AXNode* parent, int32_t id, int32_t index_in_parent); AXNode* CreateNode(AXNode* parent,
int32_t id,
int32_t index_in_parent,
AXTreeUpdateState* update_state);
// This is called from within Unserialize(), it returns true on success. // This is called from within Unserialize(), it returns true on success.
bool UpdateNode(const AXNodeData& src, bool UpdateNode(const AXNodeData& src,
......
...@@ -40,10 +40,16 @@ class FakeAXTreeDelegate : public AXTreeDelegate { ...@@ -40,10 +40,16 @@ class FakeAXTreeDelegate : public AXTreeDelegate {
subtree_deleted_ids_.push_back(node->id()); subtree_deleted_ids_.push_back(node->id());
} }
void OnNodeWillBeReparented(AXTree* tree, AXNode* node) override {}
void OnSubtreeWillBeReparented(AXTree* tree, AXNode* node) override {}
void OnNodeCreated(AXTree* tree, AXNode* node) override { void OnNodeCreated(AXTree* tree, AXNode* node) override {
created_ids_.push_back(node->id()); created_ids_.push_back(node->id());
} }
void OnNodeReparented(AXTree* tree, AXNode* node) override {}
void OnNodeChanged(AXTree* tree, AXNode* node) override { void OnNodeChanged(AXTree* tree, AXNode* node) override {
changed_ids_.push_back(node->id()); changed_ids_.push_back(node->id());
} }
...@@ -432,4 +438,45 @@ TEST(AXTreeTest, ReparentingDoesNotTriggerNodeCreated) { ...@@ -432,4 +438,45 @@ TEST(AXTreeTest, ReparentingDoesNotTriggerNodeCreated) {
node_reparented.end()); node_reparented.end());
} }
TEST(AXTreeTest, TreeDelegateIsNotCalledForReparenting) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(2);
initial_state.nodes[0].id = 1;
initial_state.nodes[0].child_ids.push_back(2);
initial_state.nodes[1].id = 2;
AXTree tree(initial_state);
AXTreeUpdate update;
update.node_id_to_clear = 1;
update.root_id = 2;
update.nodes.resize(2);
update.nodes[0].id = 2;
update.nodes[0].child_ids.push_back(4);
update.nodes[1].id = 4;
FakeAXTreeDelegate fake_delegate;
tree.SetDelegate(&fake_delegate);
EXPECT_TRUE(tree.Unserialize(update));
ASSERT_EQ(1U, fake_delegate.deleted_ids().size());
EXPECT_EQ(1, fake_delegate.deleted_ids()[0]);
ASSERT_EQ(1U, fake_delegate.subtree_deleted_ids().size());
EXPECT_EQ(1, fake_delegate.subtree_deleted_ids()[0]);
ASSERT_EQ(1U, fake_delegate.created_ids().size());
EXPECT_EQ(4, fake_delegate.created_ids()[0]);
ASSERT_EQ(0U, fake_delegate.subtree_creation_finished_ids().size());
ASSERT_EQ(1U, fake_delegate.node_creation_finished_ids().size());
EXPECT_EQ(4, fake_delegate.node_creation_finished_ids()[0]);
ASSERT_EQ(true, fake_delegate.root_changed());
tree.SetDelegate(NULL);
}
} // namespace ui } // namespace ui
...@@ -31,7 +31,10 @@ class TestAXTreeDelegate : public AXTreeDelegate { ...@@ -31,7 +31,10 @@ class TestAXTreeDelegate : public AXTreeDelegate {
} }
} }
void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) override {} void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) override {}
void OnNodeWillBeReparented(AXTree* tree, AXNode* node) override {}
void OnSubtreeWillBeReparented(AXTree* tree, AXNode* node) override {}
void OnNodeCreated(AXTree* tree, AXNode* node) override {} void OnNodeCreated(AXTree* tree, AXNode* node) override {}
void OnNodeReparented(AXTree* tree, AXNode* node) override {}
void OnNodeChanged(AXTree* tree, AXNode* node) override {} void OnNodeChanged(AXTree* tree, AXNode* node) override {}
void OnAtomicUpdateFinished(AXTree* tree, void OnAtomicUpdateFinished(AXTree* tree,
bool root_changed, bool root_changed,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment