summarylogtreecommitdiffstats
path: root/0001-Refactor-bookmark-GUID-matching-logic.patch
blob: 3c0a732efc6e6ccfce11fe28c004da645c67ef55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
From afc8770181f528d391e74af41e9be125814e3009 Mon Sep 17 00:00:00 2001
From: Mikel Astiz <mastiz@chromium.org>
Date: Mon, 25 Nov 2019 10:53:45 +0000
Subject: [PATCH] Refactor bookmark GUID-matching logic

Prior to this patch, the two existing functions to build GUID-keyed maps
were heavily coupled and there were fragile underlying assumptions that
made it work (e.g. there were potentially entries in
|guid_to_remote_node_map_| that didn't actually match any local GUID).

Instead, let's centralize the matching logic into one single function,
and adopt a unified map to represent the matches. This simplifies the
code and enforces more strict invariants without the need for DCHECKs.

Bug: 978430
Change-Id: Ieea0db8d43376e204b793dcc64b2dc84d521b289
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1929216
Commit-Queue: Mikel Astiz <mastiz@chromium.org>
Reviewed-by: Mohamed Amir Yosef <mamir@chromium.org>
Cr-Commit-Position: refs/heads/master@{#718580}
---
 .../sync_bookmarks/bookmark_model_merger.cc   | 133 ++++++++----------
 .../sync_bookmarks/bookmark_model_merger.h    |  40 ++----
 .../bookmark_specifics_conversions.cc         |   6 +
 3 files changed, 81 insertions(+), 98 deletions(-)

diff --git a/components/sync_bookmarks/bookmark_model_merger.cc b/components/sync_bookmarks/bookmark_model_merger.cc
index 861403dbe05a..dfd19a27a592 100644
--- a/components/sync_bookmarks/bookmark_model_merger.cc
+++ b/components/sync_bookmarks/bookmark_model_merger.cc
@@ -237,9 +237,8 @@ BookmarkModelMerger::BookmarkModelMerger(
       favicon_service_(favicon_service),
       bookmark_tracker_(bookmark_tracker),
       remote_forest_(BuildRemoteForest(std::move(updates))),
-      guid_to_remote_node_map_(BuildGUIDToRemoteNodeMap(remote_forest_)),
-      guid_to_local_node_map_(
-          BuildGUIDToLocalNodeMap(bookmark_model_, guid_to_remote_node_map_)) {
+      guid_to_match_map_(
+          FindGuidMatchesOrReassignLocal(remote_forest_, bookmark_model_)) {
   DCHECK(bookmark_tracker_->IsEmpty());
   DCHECK(favicon_service);
 }
@@ -307,63 +306,62 @@ BookmarkModelMerger::RemoteForest BookmarkModelMerger::BuildRemoteForest(
 }
 
 // static
-std::unordered_map<std::string, const BookmarkModelMerger::RemoteTreeNode*>
-BookmarkModelMerger::BuildGUIDToRemoteNodeMap(
-    const RemoteForest& remote_forest) {
+std::unordered_map<std::string, BookmarkModelMerger::GuidMatch>
+BookmarkModelMerger::FindGuidMatchesOrReassignLocal(
+    const RemoteForest& remote_forest,
+    bookmarks::BookmarkModel* bookmark_model) {
+  DCHECK(bookmark_model);
+
   // TODO(crbug.com/978430): Handle potential duplicate GUIDs within remote
   // updates.
-  std::unordered_map<std::string, const RemoteTreeNode*>
-      guid_to_remote_node_map;
+
   if (!base::FeatureList::IsEnabled(switches::kMergeBookmarksUsingGUIDs)) {
-    return guid_to_remote_node_map;
+    return {};
   }
 
+  // Build a temporary lookup table for remote GUIDs.
+  std::unordered_map<std::string, const RemoteTreeNode*>
+      guid_to_remote_node_map;
   for (const auto& tree_tag_and_root : remote_forest) {
     tree_tag_and_root.second.EmplaceSelfAndDescendantsByGUID(
         &guid_to_remote_node_map);
   }
 
-  return guid_to_remote_node_map;
-}
-
-// static
-std::unordered_map<std::string, const bookmarks::BookmarkNode*>
-BookmarkModelMerger::BuildGUIDToLocalNodeMap(
-    bookmarks::BookmarkModel* bookmark_model,
-    const std::unordered_map<std::string, const RemoteTreeNode*>&
-        guid_to_remote_node_map) {
-  DCHECK(bookmark_model);
-
-  std::unordered_map<std::string, const bookmarks::BookmarkNode*>
-      guid_to_local_node_map;
-  if (!base::FeatureList::IsEnabled(switches::kMergeBookmarksUsingGUIDs)) {
-    return guid_to_local_node_map;
-  }
-
+  // Iterate through all local bookmarks to find matches by GUID.
+  std::unordered_map<std::string, BookmarkModelMerger::GuidMatch>
+      guid_to_match_map;
   ui::TreeNodeIterator<const bookmarks::BookmarkNode> iterator(
       bookmark_model->root_node());
   while (iterator.has_next()) {
-    const bookmarks::BookmarkNode* node = iterator.Next();
+    const bookmarks::BookmarkNode* const node = iterator.Next();
     DCHECK(base::IsValidGUID(node->guid()));
+
     if (node->is_permanent_node()) {
       continue;
     }
+
     const auto remote_it = guid_to_remote_node_map.find(node->guid());
     if (remote_it == guid_to_remote_node_map.end()) {
       continue;
     }
-    // If local node and its remote node match are conflicting in node type or
-    // URL, replace local GUID with a random GUID.
-    const syncer::EntityData& remote_entity = remote_it->second->entity();
+
+    const RemoteTreeNode* const remote_node = remote_it->second;
+    const syncer::EntityData& remote_entity = remote_node->entity();
     if (node->is_folder() != remote_entity.is_folder ||
         (node->is_url() &&
          node->url() != remote_entity.specifics.bookmark().url())) {
-      node =
-          ReplaceBookmarkNodeGUID(node, base::GenerateGUID(), bookmark_model);
+      // If local node and its remote node match are conflicting in node type or
+      // URL, replace local GUID with a random GUID.
+      // TODO(crbug.com/978430): Local GUIDs should also be reassigned if they
+      // match a remote originator_item_id.
+      ReplaceBookmarkNodeGUID(node, base::GenerateGUID(), bookmark_model);
+      continue;
     }
-    guid_to_local_node_map.emplace(node->guid(), node);
+
+    guid_to_match_map.emplace(node->guid(), GuidMatch{node, remote_node});
   }
-  return guid_to_local_node_map;
+
+  return guid_to_match_map;
 }
 
 void BookmarkModelMerger::MergeSubtree(
@@ -422,25 +420,27 @@ const bookmarks::BookmarkNode* BookmarkModelMerger::FindMatchingLocalNode(
     const bookmarks::BookmarkNode* local_parent,
     size_t local_child_start_index) const {
   // Try to match child by GUID. If we can't, try to match child by semantics.
-  const bookmarks::BookmarkNode* matching_local_node =
+  const bookmarks::BookmarkNode* matching_local_node_by_guid =
       FindMatchingLocalNodeByGUID(remote_child);
-  if (!matching_local_node) {
-    // All local nodes up to |remote_index-1| have processed already. Look for a
-    // matching local node starting with the local node at position
-    // |local_child_start_index|. FindMatchingChildBySemanticsStartingAt()
-    // returns kInvalidIndex in the case where no semantics match was found or
-    // the semantics match found is GUID-matchable to a different node.
-    const size_t local_index = FindMatchingChildBySemanticsStartingAt(
-        /*remote_node=*/remote_child,
-        /*local_parent=*/local_parent,
-        /*starting_child_index=*/local_child_start_index);
-    if (local_index == kInvalidIndex) {
-      // If no match found, return.
-      return nullptr;
-    }
-    matching_local_node = local_parent->children()[local_index].get();
+  if (matching_local_node_by_guid) {
+    return matching_local_node_by_guid;
+  }
+
+  // All local nodes up to |remote_index-1| have processed already. Look for a
+  // matching local node starting with the local node at position
+  // |local_child_start_index|. FindMatchingChildBySemanticsStartingAt()
+  // returns kInvalidIndex in the case where no semantics match was found or
+  // the semantics match found is GUID-matchable to a different node.
+  const size_t local_index = FindMatchingChildBySemanticsStartingAt(
+      /*remote_node=*/remote_child,
+      /*local_parent=*/local_parent,
+      /*starting_child_index=*/local_child_start_index);
+  if (local_index == kInvalidIndex) {
+    // If no match found, return.
+    return nullptr;
   }
-  return matching_local_node;
+
+  return local_parent->children()[local_index].get();
 }
 
 const bookmarks::BookmarkNode*
@@ -471,8 +471,6 @@ BookmarkModelMerger::UpdateBookmarkNodeFromSpecificsIncludingGUID(
     return local_node;
   }
   DCHECK(base::IsValidGUID(specifics.guid()));
-  // We do not update the GUID maps upon node replacement as per the comment
-  // in bookmark_model_merger.h.
   return ReplaceBookmarkNodeGUID(local_node, specifics.guid(), bookmark_model_);
 }
 
@@ -574,7 +572,7 @@ void BookmarkModelMerger::ProcessLocalCreation(
     if (FindMatchingRemoteNodeByGUID(node->children()[i].get())) {
       continue;
     }
-    ProcessLocalCreation(node, i);
+    ProcessLocalCreation(/*parent=*/node, i);
   }
 }
 
@@ -612,36 +610,25 @@ const BookmarkModelMerger::RemoteTreeNode*
 BookmarkModelMerger::FindMatchingRemoteNodeByGUID(
     const bookmarks::BookmarkNode* local_node) const {
   DCHECK(local_node);
-  // Ensure matching nodes are of the same type and have the same URL,
-  // guaranteed by BuildGUIDToLocalNodeMap().
-  const auto it = guid_to_remote_node_map_.find(local_node->guid());
-  if (it == guid_to_remote_node_map_.end()) {
+
+  const auto it = guid_to_match_map_.find(local_node->guid());
+  if (it == guid_to_match_map_.end()) {
     return nullptr;
   }
 
-  const RemoteTreeNode* const remote_node = it->second;
-  DCHECK(remote_node);
-  DCHECK_EQ(local_node->is_folder(), remote_node->entity().is_folder);
-  DCHECK_EQ(local_node->url(),
-            remote_node->entity().specifics.bookmark().url());
-  return remote_node;
+  return it->second.remote_node;
 }
 
 const bookmarks::BookmarkNode* BookmarkModelMerger::FindMatchingLocalNodeByGUID(
     const RemoteTreeNode& remote_node) const {
   const syncer::EntityData& remote_entity = remote_node.entity();
   const auto it =
-      guid_to_local_node_map_.find(remote_entity.specifics.bookmark().guid());
-  if (it == guid_to_local_node_map_.end()) {
+      guid_to_match_map_.find(remote_entity.specifics.bookmark().guid());
+  if (it == guid_to_match_map_.end()) {
     return nullptr;
   }
-  DCHECK(!remote_entity.specifics.bookmark().guid().empty());
-  const bookmarks::BookmarkNode* local_node = it->second;
-  // Ensure matching nodes are of the same type and have the same URL,
-  // guaranteed by BuildGUIDToLocalNodeMap().
-  DCHECK_EQ(local_node->is_folder(), remote_entity.is_folder);
-  DCHECK_EQ(local_node->url(), remote_entity.specifics.bookmark().url());
-  return local_node;
+
+  return it->second.local_node;
 }
 
 }  // namespace sync_bookmarks
diff --git a/components/sync_bookmarks/bookmark_model_merger.h b/components/sync_bookmarks/bookmark_model_merger.h
index 1a40fad2a074..18744920a1a4 100644
--- a/components/sync_bookmarks/bookmark_model_merger.h
+++ b/components/sync_bookmarks/bookmark_model_merger.h
@@ -56,6 +56,14 @@ class BookmarkModelMerger {
   // a permanent node, keyed by server-defined unique tag of the root.
   using RemoteForest = std::unordered_map<std::string, RemoteTreeNode>;
 
+  // Represents a pair of bookmarks, one local and one remote, that have been
+  // matched by GUID. They are guaranteed to have the same type and URL (if
+  // applicable).
+  struct GuidMatch {
+    const bookmarks::BookmarkNode* local_node;
+    const RemoteTreeNode* remote_node;
+  };
+
   // Constructs the remote bookmark tree to be merged. Each entry in the
   // returned map is a permanent node, identified (keyed) by the server-defined
   // tag. All invalid updates are filtered out, including invalid bookmark
@@ -63,22 +71,12 @@ class BookmarkModelMerger {
   // sends tombstones as part of the initial download.
   static RemoteForest BuildRemoteForest(syncer::UpdateResponseDataList updates);
 
-  // Constructs a map from GUID to corresponding remote nodes, used in the merge
-  // process to determine GUID-based node matches.
-  static std::unordered_map<std::string, const RemoteTreeNode*>
-  BuildGUIDToRemoteNodeMap(const RemoteForest& remote_forest);
-
-  // Constructs a map from GUID to corresponding local node, used in the merge
-  // process to determine GUID-based node matches. |bookmark_model| must not be
-  // null. |guid_to_remote_node_map| is used to detect conflicting GUID matches
-  // between local and remote bookmarks for the cases where they cannot be
-  // merged (e.g. folder vs non-folder) and in such cases regenerate a random
-  // GUID for the local bookmark.
-  static std::unordered_map<std::string, const bookmarks::BookmarkNode*>
-  BuildGUIDToLocalNodeMap(
-      bookmarks::BookmarkModel* bookmark_model,
-      const std::unordered_map<std::string, const RemoteTreeNode*>&
-          guid_to_remote_node_map);
+  // Computes bookmark pairs that should be matched by GUID. Local bookmark
+  // GUIDs may be regenerated for the case where they collide with a remote GUID
+  // that is not compatible (e.g. folder vs non-folder).
+  static std::unordered_map<std::string, GuidMatch>
+  FindGuidMatchesOrReassignLocal(const RemoteForest& remote_forest,
+                                 bookmarks::BookmarkModel* bookmark_model);
 
   // Merges a local and a remote subtrees. The input nodes are two equivalent
   // local and remote nodes. This method tries to recursively match their
@@ -151,15 +149,7 @@ class BookmarkModelMerger {
   // Preprocessed remote nodes in the form a forest where each tree's root is a
   // permanent node. Computed upon construction via BuildRemoteForest().
   const RemoteForest remote_forest_;
-  // Maps GUIDs to their respective remote nodes. Used for GUID-based node
-  // matching and is populated at the beginning of the merge process.
-  const std::unordered_map<std::string, const RemoteTreeNode*>
-      guid_to_remote_node_map_;
-  // Maps GUIDs to their respective local nodes. Used for GUID-based
-  // node matching and is populated at the beginning of the merge process.
-  // Is not updated upon potential changes to the model during merge.
-  const std::unordered_map<std::string, const bookmarks::BookmarkNode*>
-      guid_to_local_node_map_;
+  std::unordered_map<std::string, GuidMatch> guid_to_match_map_;
 
   DISALLOW_COPY_AND_ASSIGN(BookmarkModelMerger);
 };
diff --git a/components/sync_bookmarks/bookmark_specifics_conversions.cc b/components/sync_bookmarks/bookmark_specifics_conversions.cc
index 98725dce2a61..74914f187509 100644
--- a/components/sync_bookmarks/bookmark_specifics_conversions.cc
+++ b/components/sync_bookmarks/bookmark_specifics_conversions.cc
@@ -255,6 +255,12 @@ const bookmarks::BookmarkNode* ReplaceBookmarkNodeGUID(
   }
   const bookmarks::BookmarkNode* new_node;
   DCHECK(base::IsValidGUID(guid));
+
+  if (node->guid() == guid) {
+    // Nothing to do.
+    return node;
+  }
+
   if (node->is_folder()) {
     new_node =
         model->AddFolder(node->parent(), node->parent()->GetIndexOf(node),
-- 
2.24.1