Commit 50da791e authored by Dominic Mazzoni's avatar Dominic Mazzoni Committed by Commit Bot

Revert "Re-land: Avoid serializing the same accessibility node twice in the same message"

Speculative revert, see build failures on Win10 Tests x64 (dbg):
https://ci.chromium.org/p/chromium/builders/luci.chromium.ci/Win10%20Tests%20x64%20%28dbg%29/803

Bug: http://crbug.com/846837

This reverts commit 983e926b.

Reason for revert: <INSERT REASONING HERE>

Original change's description:
> Re-land: Avoid serializing the same accessibility node twice in the same message
> 
> Originally landed: r560165, crrev.com/c/1063007
> Reverted: r561246, crrev.com/c/1069891
> 
> TBR=dtseng@chromium.org
> Bug: 651614, 845778
> 
> Change-Id: I77b54de28cb0c4a231b5b0758ba4708100d27fff
> Reviewed-on: https://chromium-review.googlesource.com/1072875
> Reviewed-by: Dominic Mazzoni <dmazzoni@chromium.org>
> Commit-Queue: Dominic Mazzoni <dmazzoni@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#561913}

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

Change-Id: I5cbf8006e4917a9c008143ae4b746543d2b038c2
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 651614, 845778, 846837
Reviewed-on: https://chromium-review.googlesource.com/1074050Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Dominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#562043}
parent add2efb3
...@@ -196,8 +196,7 @@ void AutomationManagerAura::SendEvent(BrowserContext* context, ...@@ -196,8 +196,7 @@ void AutomationManagerAura::SendEvent(BrowserContext* context,
std::vector<ExtensionMsg_AccessibilityEventParams> events; std::vector<ExtensionMsg_AccessibilityEventParams> events;
events.emplace_back(ExtensionMsg_AccessibilityEventParams()); events.emplace_back(ExtensionMsg_AccessibilityEventParams());
ExtensionMsg_AccessibilityEventParams& params = events.back(); ExtensionMsg_AccessibilityEventParams& params = events.back();
current_tree_serializer_->BeginSerializingChanges(&params.update); if (!current_tree_serializer_->SerializeChanges(aura_obj, &params.update)) {
if (!current_tree_serializer_->SerializeOneChange(aura_obj)) {
LOG(ERROR) << "Unable to serialize one accessibility event."; LOG(ERROR) << "Unable to serialize one accessibility event.";
return; return;
} }
...@@ -206,9 +205,7 @@ void AutomationManagerAura::SendEvent(BrowserContext* context, ...@@ -206,9 +205,7 @@ void AutomationManagerAura::SendEvent(BrowserContext* context,
views::AXAuraObjWrapper* focus = views::AXAuraObjWrapper* focus =
views::AXAuraObjCache::GetInstance()->GetFocus(); views::AXAuraObjCache::GetInstance()->GetFocus();
if (focus) if (focus)
current_tree_serializer_->SerializeOneChange(focus); current_tree_serializer_->SerializeChanges(focus, &params.update);
current_tree_serializer_->FinishSerializingChanges();
params.tree_id = 0; params.tree_id = 0;
params.id = aura_obj->GetUniqueId().Get(); params.id = aura_obj->GetUniqueId().Get();
......
...@@ -141,8 +141,7 @@ void AutomationManagerAura::SendEvent(BrowserContext* context, ...@@ -141,8 +141,7 @@ void AutomationManagerAura::SendEvent(BrowserContext* context,
std::vector<ExtensionMsg_AccessibilityEventParams> events; std::vector<ExtensionMsg_AccessibilityEventParams> events;
events.emplace_back(ExtensionMsg_AccessibilityEventParams()); events.emplace_back(ExtensionMsg_AccessibilityEventParams());
ExtensionMsg_AccessibilityEventParams& params = events.back(); ExtensionMsg_AccessibilityEventParams& params = events.back();
current_tree_serializer_->BeginSerializingChanges(&params.update); if (!current_tree_serializer_->SerializeChanges(aura_obj, &params.update)) {
if (!current_tree_serializer_->SerializeOneChange(aura_obj)) {
LOG(ERROR) << "Unable to serialize one accessibility event."; LOG(ERROR) << "Unable to serialize one accessibility event.";
return; return;
} }
...@@ -151,9 +150,7 @@ void AutomationManagerAura::SendEvent(BrowserContext* context, ...@@ -151,9 +150,7 @@ void AutomationManagerAura::SendEvent(BrowserContext* context,
views::AXAuraObjWrapper* focus = views::AXAuraObjWrapper* focus =
views::AXAuraObjCache::GetInstance()->GetFocus(); views::AXAuraObjCache::GetInstance()->GetFocus();
if (focus) if (focus)
current_tree_serializer_->SerializeOneChange(focus); current_tree_serializer_->SerializeChanges(focus, &params.update);
current_tree_serializer_->FinishSerializingChanges();
params.tree_id = 0; params.tree_id = 0;
params.id = aura_obj->GetUniqueId().Get(); params.id = aura_obj->GetUniqueId().Get();
......
...@@ -454,15 +454,8 @@ void RenderAccessibilityImpl::SendPendingAccessibilityEvents() { ...@@ -454,15 +454,8 @@ void RenderAccessibilityImpl::SendPendingAccessibilityEvents() {
if (plugin_tree_source_) if (plugin_tree_source_)
event_msg.update.has_tree_data = true; event_msg.update.has_tree_data = true;
if (event_msgs.size() == 0) if (!serializer_.SerializeChanges(obj, &event_msg.update)) {
serializer_.BeginSerializingChanges(&event_msg.update);
if (!serializer_.SerializeOneChange(obj)) {
VLOG(1) << "Failed to serialize one accessibility event."; VLOG(1) << "Failed to serialize one accessibility event.";
if (event_msgs.size() == 0)
serializer_.FinishSerializingChanges();
continue; continue;
} }
...@@ -488,9 +481,6 @@ void RenderAccessibilityImpl::SendPendingAccessibilityEvents() { ...@@ -488,9 +481,6 @@ void RenderAccessibilityImpl::SendPendingAccessibilityEvents() {
<< "\n" << event_msg.update.ToString(); << "\n" << event_msg.update.ToString();
} }
if (event_msgs.size())
serializer_.FinishSerializingChanges();
Send(new AccessibilityHostMsg_Events(routing_id(), event_msgs, reset_token_, Send(new AccessibilityHostMsg_Events(routing_id(), event_msgs, reset_token_,
ack_token_)); ack_token_));
reset_token_ = 0; reset_token_ = 0;
......
...@@ -76,23 +76,10 @@ class AXTreeSerializer { ...@@ -76,23 +76,10 @@ class AXTreeSerializer {
max_node_count_ = max_node_count; max_node_count_ = max_node_count;
} }
// Serialize a sequence of one or more changes. This is more efficient
// than calling SerializeChanges more than once with the same output
// AXTreeUpdate; it will automatically avoid serializing the same
// node more than once when there are overlapping changes.
// It's an error to call DeleteClientSubtree in the middle of a sequence.
void BeginSerializingChanges(
AXTreeUpdateBase<AXNodeData, AXTreeData>* out_update);
bool SerializeOneChange(AXSourceNode node);
void FinishSerializingChanges();
// Serialize all changes to |node| and append them to |out_update|. // Serialize all changes to |node| and append them to |out_update|.
// Returns true on success. On failure, returns false and calls Reset(); // Returns true on success. On failure, returns false and calls Reset();
// this only happens when the source tree has a problem like duplicate // this only happens when the source tree has a problem like duplicate
// ids or changing during serialization. Note that it's more efficient // ids or changing during serialization.
// to use BeginSerializingChanges, SerializeOneChange, and
// FinishSerializingChanges if you're going to include more than one
// change in the same output AXTreeUpdate.
bool SerializeChanges(AXSourceNode node, bool SerializeChanges(AXSourceNode node,
AXTreeUpdateBase<AXNodeData, AXTreeData>* out_update); AXTreeUpdateBase<AXNodeData, AXTreeData>* out_update);
...@@ -165,7 +152,9 @@ class AXTreeSerializer { ...@@ -165,7 +152,9 @@ class AXTreeSerializer {
void DeleteClientSubtree(ClientTreeNode* client_node); void DeleteClientSubtree(ClientTreeNode* client_node);
// Helper function, called recursively with each new node to serialize. // Helper function, called recursively with each new node to serialize.
bool SerializeChangedNodes(AXSourceNode node); bool SerializeChangedNodes(
AXSourceNode node,
AXTreeUpdateBase<AXNodeData, AXTreeData>* out_update);
// Visit all of the descendants of |node| once. // Visit all of the descendants of |node| once.
void WalkAllDescendants(AXSourceNode node); void WalkAllDescendants(AXSourceNode node);
...@@ -182,14 +171,6 @@ class AXTreeSerializer { ...@@ -182,14 +171,6 @@ class AXTreeSerializer {
// A map from IDs to nodes in the client tree. // A map from IDs to nodes in the client tree.
base::hash_map<int32_t, ClientTreeNode*> client_id_map_; base::hash_map<int32_t, ClientTreeNode*> client_id_map_;
// The current update, valid in-between calls to BeginSerializingChanges
// and FinishSerializingChanges.
AXTreeUpdateBase<AXNodeData, AXTreeData>* current_update_ = nullptr;
// The set of IDs already serialized, valid in-between calls to
// BeginSerializingChanges and FinishSerializingChanges.
std::set<int32_t> already_serialized_ids_;
// The maximum number of nodes to serialize in a given call to // The maximum number of nodes to serialize in a given call to
// SerializeChanges, or 0 if there's no maximum. // SerializeChanges, or 0 if there's no maximum.
size_t max_node_count_ = 0; size_t max_node_count_ = 0;
...@@ -339,25 +320,16 @@ AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::ClientTreeNodeById( ...@@ -339,25 +320,16 @@ AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::ClientTreeNodeById(
} }
template <typename AXSourceNode, typename AXNodeData, typename AXTreeData> template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
void AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>:: bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::SerializeChanges(
BeginSerializingChanges( AXSourceNode node,
AXTreeUpdateBase<AXNodeData, AXTreeData>* out_update) { AXTreeUpdateBase<AXNodeData, AXTreeData>* out_update) {
CHECK(!current_update_);
current_update_ = out_update;
}
template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::SerializeOneChange(
AXSourceNode node) {
CHECK(current_update_);
// Send the tree data if it's changed since the last update, or if // Send the tree data if it's changed since the last update, or if
// current_update_->has_tree_data is already set to true. // out_update->has_tree_data is already set to true.
AXTreeData new_tree_data; AXTreeData new_tree_data;
if (tree_->GetTreeData(&new_tree_data) && if (tree_->GetTreeData(&new_tree_data) &&
(current_update_->has_tree_data || new_tree_data != client_tree_data_)) { (out_update->has_tree_data || new_tree_data != client_tree_data_)) {
current_update_->has_tree_data = true; out_update->has_tree_data = true;
current_update_->tree_data = new_tree_data; out_update->tree_data = new_tree_data;
client_tree_data_ = new_tree_data; client_tree_data_ = new_tree_data;
} }
...@@ -385,13 +357,13 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::SerializeOneChange( ...@@ -385,13 +357,13 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::SerializeOneChange(
if (!tree_->IsValid(lca)) { if (!tree_->IsValid(lca)) {
// If there's no LCA, just tell the client to destroy the whole // If there's no LCA, just tell the client to destroy the whole
// tree and then we'll serialize everything from the new root. // tree and then we'll serialize everything from the new root.
current_update_->node_id_to_clear = client_root_->id; out_update->node_id_to_clear = client_root_->id;
Reset(); Reset();
} else if (need_delete) { } else if (need_delete) {
// Otherwise, if we need to reserialize a subtree, first we need // Otherwise, if we need to reserialize a subtree, first we need
// to delete those nodes in our client tree so that // to delete those nodes in our client tree so that
// SerializeChangedNodes() will be sure to send them again. // SerializeChangedNodes() will be sure to send them again.
current_update_->node_id_to_clear = tree_->GetId(lca); out_update->node_id_to_clear = tree_->GetId(lca);
ClientTreeNode* client_lca = ClientTreeNodeById(tree_->GetId(lca)); ClientTreeNode* client_lca = ClientTreeNodeById(tree_->GetId(lca));
CHECK(client_lca); CHECK(client_lca);
DeleteClientSubtree(client_lca); DeleteClientSubtree(client_lca);
...@@ -409,34 +381,13 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::SerializeOneChange( ...@@ -409,34 +381,13 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::SerializeOneChange(
// DumpAccessibilityTreeTest.AccessibilityAriaOwns. // DumpAccessibilityTreeTest.AccessibilityAriaOwns.
WalkAllDescendants(lca); WalkAllDescendants(lca);
return SerializeChangedNodes(lca); return SerializeChangedNodes(lca, out_update);
}
template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
void AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::
FinishSerializingChanges() {
CHECK(current_update_);
current_update_ = nullptr;
already_serialized_ids_.clear();
}
template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::SerializeChanges(
AXSourceNode node,
AXTreeUpdateBase<AXNodeData, AXTreeData>* out_update) {
CHECK(!current_update_);
BeginSerializingChanges(out_update);
bool success = SerializeOneChange(node);
FinishSerializingChanges();
return success;
} }
template <typename AXSourceNode, typename AXNodeData, typename AXTreeData> template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
void AXTreeSerializer<AXSourceNode, void AXTreeSerializer<AXSourceNode,
AXNodeData, AXNodeData,
AXTreeData>::DeleteClientSubtree(AXSourceNode node) { AXTreeData>::DeleteClientSubtree(AXSourceNode node) {
CHECK(!current_update_);
ClientTreeNode* client_node = ClientTreeNodeById(tree_->GetId(node)); ClientTreeNode* client_node = ClientTreeNodeById(tree_->GetId(node));
if (client_node) if (client_node)
DeleteClientSubtree(client_node); DeleteClientSubtree(client_node);
...@@ -455,7 +406,9 @@ void AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>:: ...@@ -455,7 +406,9 @@ void AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::
template <typename AXSourceNode, typename AXNodeData, typename AXTreeData> template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>:: bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::
SerializeChangedNodes(AXSourceNode node) { SerializeChangedNodes(
AXSourceNode node,
AXTreeUpdateBase<AXNodeData, AXTreeData>* out_update) {
// This method has three responsibilities: // This method has three responsibilities:
// 1. Serialize |node| into an AXNodeData, and append it to // 1. Serialize |node| into an AXNodeData, and append it to
// the AXTreeUpdate to be sent to the client. // the AXTreeUpdate to be sent to the client.
...@@ -469,10 +422,6 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>:: ...@@ -469,10 +422,6 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::
// about. If we don't find it, then this must be the new root of the // about. If we don't find it, then this must be the new root of the
// accessibility tree. // accessibility tree.
int id = tree_->GetId(node); int id = tree_->GetId(node);
if (already_serialized_ids_.find(id) != already_serialized_ids_.end())
return true;
already_serialized_ids_.insert(id);
ClientTreeNode* client_node = ClientTreeNodeById(id); ClientTreeNode* client_node = ClientTreeNodeById(id);
if (!client_node) { if (!client_node) {
Reset(); Reset();
...@@ -491,7 +440,7 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>:: ...@@ -491,7 +440,7 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::
// consistent results. // consistent results.
base::hash_set<int32_t> new_child_ids; base::hash_set<int32_t> new_child_ids;
std::vector<AXSourceNode> children; std::vector<AXSourceNode> children;
if (max_node_count_ == 0 || current_update_->nodes.size() < max_node_count_) { if (max_node_count_ == 0 || out_update->nodes.size() < max_node_count_) {
tree_->GetChildren(node, &children); tree_->GetChildren(node, &children);
} else if (max_node_count_ > 0) { } else if (max_node_count_ > 0) {
static bool logged_once = false; static bool logged_once = false;
...@@ -538,18 +487,17 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>:: ...@@ -538,18 +487,17 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::
// Serialize this node. This fills in all of the fields in // Serialize this node. This fills in all of the fields in
// AXNodeData except child_ids, which we handle below. // AXNodeData except child_ids, which we handle below.
size_t serialized_node_index = current_update_->nodes.size(); size_t serialized_node_index = out_update->nodes.size();
current_update_->nodes.push_back(AXNodeData()); out_update->nodes.push_back(AXNodeData());
{ {
// Take the address of an element in a vector only within a limited // Take the address of an element in a vector only within a limited
// scope because otherwise the pointer can become invalid if the // scope because otherwise the pointer can become invalid if the
// vector is resized. // vector is resized.
AXNodeData* serialized_node = AXNodeData* serialized_node = &out_update->nodes[serialized_node_index];
&current_update_->nodes[serialized_node_index];
tree_->SerializeNode(node, serialized_node); tree_->SerializeNode(node, serialized_node);
if (serialized_node->id == client_root_->id) if (serialized_node->id == client_root_->id)
current_update_->root_id = serialized_node->id; out_update->root_id = serialized_node->id;
} }
// Iterate over the children, serialize them, and update the ClientTreeNode // Iterate over the children, serialize them, and update the ClientTreeNode
...@@ -579,14 +527,14 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>:: ...@@ -579,14 +527,14 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::
new_child->parent = client_node; new_child->parent = client_node;
client_node->children.push_back(new_child); client_node->children.push_back(new_child);
client_id_map_[child_id] = new_child; client_id_map_[child_id] = new_child;
if (!SerializeChangedNodes(child)) if (!SerializeChangedNodes(child, out_update))
return false; return false;
} }
} }
// Finally, update the child ids of this node to reflect the actual child // Finally, update the child ids of this node to reflect the actual child
// ids that were valid during serialization. // ids that were valid during serialization.
current_update_->nodes[serialized_node_index].child_ids.swap( out_update->nodes[serialized_node_index].child_ids.swap(
actual_serialized_node_child_ids); actual_serialized_node_child_ids);
return true; return true;
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "testing/gtest/include/gtest/gtest-death-test.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_node.h" #include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_serializable_tree.h" #include "ui/accessibility/ax_serializable_tree.h"
...@@ -344,96 +343,4 @@ TEST_F(AXTreeSerializerTest, DuplicateIdsReturnsErrorAndFlushes) { ...@@ -344,96 +343,4 @@ TEST_F(AXTreeSerializerTest, DuplicateIdsReturnsErrorAndFlushes) {
ASSERT_EQ(static_cast<size_t>(5), update.nodes.size()); ASSERT_EQ(static_cast<size_t>(5), update.nodes.size());
} }
TEST_F(AXTreeSerializerTest, SerializeOneChangeDeathTest) {
treedata0_.root_id = 1;
treedata0_.nodes.resize(1);
treedata0_.nodes[0].id = 1;
treedata1_.root_id = 1;
treedata1_.nodes.resize(1);
treedata1_.nodes[0].id = 1;
CreateTreeSerializer();
// It's not legal to call SerializeOneChange without calling
// BeginSerializingChanges first.
ASSERT_DEATH_IF_SUPPORTED(
{ serializer_->SerializeOneChange(tree1_->GetFromId(1)); }, ".*");
}
TEST_F(AXTreeSerializerTest, DeleteClientSubtreeDeathTest) {
treedata0_.root_id = 1;
treedata0_.nodes.resize(1);
treedata0_.nodes[0].id = 1;
treedata1_.root_id = 1;
treedata1_.nodes.resize(1);
treedata1_.nodes[0].id = 1;
CreateTreeSerializer();
// It's not legal to call DeleteClientSubtree in the middle of a
// serialization sequence.
AXTreeUpdate update;
serializer_->BeginSerializingChanges(&update);
ASSERT_DEATH_IF_SUPPORTED(
{ serializer_->DeleteClientSubtree(tree1_->GetFromId(1)); }, ".*");
}
TEST_F(AXTreeSerializerTest, BeginSerializingChangesTwiceDeathTest) {
treedata0_.root_id = 1;
treedata0_.nodes.resize(1);
treedata0_.nodes[0].id = 1;
treedata1_.root_id = 1;
treedata1_.nodes.resize(1);
treedata1_.nodes[0].id = 1;
CreateTreeSerializer();
AXTreeUpdate update;
serializer_->BeginSerializingChanges(&update);
ASSERT_DEATH_IF_SUPPORTED({ serializer_->BeginSerializingChanges(&update); },
".*");
}
TEST_F(AXTreeSerializerTest, SerializationSequence) {
// (1 (2 3))
treedata0_.root_id = 1;
treedata0_.nodes.resize(3);
treedata0_.nodes[0].id = 1;
treedata0_.nodes[0].child_ids.push_back(2);
treedata0_.nodes[0].child_ids.push_back(3);
treedata0_.nodes[1].id = 2;
treedata0_.nodes[2].id = 3;
// (1 (4 2 3))
treedata1_.root_id = 1;
treedata1_.nodes.resize(4);
treedata1_.nodes[0].id = 1;
treedata1_.nodes[0].child_ids.push_back(4);
treedata1_.nodes[0].child_ids.push_back(2);
treedata1_.nodes[0].child_ids.push_back(3);
treedata1_.nodes[1].id = 2;
treedata1_.nodes[2].id = 3;
treedata1_.nodes[3].id = 4;
CreateTreeSerializer();
AXTreeUpdate update;
serializer_->BeginSerializingChanges(&update);
EXPECT_EQ(0U, update.nodes.size());
ASSERT_TRUE(serializer_->SerializeOneChange(tree1_->GetFromId(1)));
EXPECT_EQ(2U, update.nodes.size());
// If we serialize the same node again, nothing should happen.
ASSERT_TRUE(serializer_->SerializeOneChange(tree1_->GetFromId(1)));
EXPECT_EQ(2U, update.nodes.size());
// If we serialize a different node, we should get it serialized.
ASSERT_TRUE(serializer_->SerializeOneChange(tree1_->GetFromId(2)));
EXPECT_EQ(3U, update.nodes.size());
serializer_->FinishSerializingChanges();
}
} // namespace ui } // namespace ui
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment