parent_guid_preprocessing.cc 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/sync_bookmarks/parent_guid_preprocessing.h"
  5. #include <memory>
  6. #include <unordered_map>
  7. #include "base/check.h"
  8. #include "base/guid.h"
  9. #include "base/memory/raw_ptr.h"
  10. #include "base/strings/string_piece.h"
  11. #include "components/bookmarks/browser/bookmark_node.h"
  12. #include "components/sync/protocol/bookmark_specifics.pb.h"
  13. #include "components/sync/protocol/entity_specifics.pb.h"
  14. #include "components/sync/protocol/model_type_state.pb.h"
  15. #include "components/sync_bookmarks/synced_bookmark_tracker.h"
  16. #include "components/sync_bookmarks/synced_bookmark_tracker_entity.h"
  17. namespace sync_bookmarks {
  18. namespace {
  19. // The tag used in the sync protocol to identity well-known permanent folders.
  20. const char kBookmarkBarTag[] = "bookmark_bar";
  21. const char kMobileBookmarksTag[] = "synced_bookmarks";
  22. const char kOtherBookmarksTag[] = "other_bookmarks";
  23. // Fake GUID used to populate field |BookmarkSpecifics.parent_guid| for the case
  24. // where a parent is specified in |SyncEntity.parent_id| but the parent's
  25. // precise GUID could not be determined. Doing this is mostly relevant for UMA
  26. // metrics. The precise GUID used in this string was generated using the same
  27. // technique as the well-known GUIDs in bookmarks::BookmarkNode, using the name
  28. // "unknown_parent_guid". The precise value is irrelevant though and can be
  29. // changed since all updates using the parent GUID will be ignored in practice.
  30. const char kInvalidParentGuid[] = "220a410e-37b9-5bbc-8674-ea982459f940";
  31. bool NeedsParentGuidInSpecifics(const syncer::UpdateResponseData& update) {
  32. return !update.entity.is_deleted() &&
  33. update.entity.legacy_parent_id != std::string("0") &&
  34. update.entity.server_defined_unique_tag.empty() &&
  35. !update.entity.specifics.bookmark().has_parent_guid();
  36. }
  37. // Tried to use the information known by |tracker| to determine the GUID of the
  38. // parent folder, for the entity updated in |update|. Returns an invalid GUID
  39. // if the GUID could not be determined. |tracker| must not be null.
  40. base::GUID TryGetParentGuidFromTracker(
  41. const SyncedBookmarkTracker* tracker,
  42. const syncer::UpdateResponseData& update) {
  43. DCHECK(tracker);
  44. DCHECK(!update.entity.is_deleted());
  45. DCHECK(!update.entity.legacy_parent_id.empty());
  46. DCHECK(update.entity.server_defined_unique_tag.empty());
  47. DCHECK(!update.entity.specifics.bookmark().has_parent_guid());
  48. const SyncedBookmarkTrackerEntity* const tracked_parent =
  49. tracker->GetEntityForSyncId(update.entity.legacy_parent_id);
  50. if (!tracked_parent) {
  51. // Parent not known by tracker.
  52. return base::GUID();
  53. }
  54. if (!tracked_parent->bookmark_node()) {
  55. // Parent is a tombstone; cannot determine the GUID.
  56. return base::GUID();
  57. }
  58. return tracked_parent->bookmark_node()->guid();
  59. }
  60. base::StringPiece GetGuidForEntity(const syncer::EntityData& entity) {
  61. // Special-case permanent folders, which may not include a GUID in specifics.
  62. if (entity.server_defined_unique_tag == kBookmarkBarTag) {
  63. return bookmarks::BookmarkNode::kBookmarkBarNodeGuid;
  64. }
  65. if (entity.server_defined_unique_tag == kOtherBookmarksTag) {
  66. return bookmarks::BookmarkNode::kOtherBookmarksNodeGuid;
  67. }
  68. if (entity.server_defined_unique_tag == kMobileBookmarksTag) {
  69. return bookmarks::BookmarkNode::kMobileBookmarksNodeGuid;
  70. }
  71. // Fall back to the regular case, i.e. GUID in specifics, or an empty value
  72. // if not present (including tombstones).
  73. return entity.specifics.bookmark().guid();
  74. }
  75. // Map from sync IDs (server-provided entity IDs) to GUIDs. The
  76. // returned map uses StringPiece that rely on the lifetime of the strings in
  77. // |updates|. |updates| must not be null.
  78. class LazySyncIdToGuidMapInUpdates {
  79. public:
  80. // |updates| must not be null and must outlive this object.
  81. explicit LazySyncIdToGuidMapInUpdates(
  82. const syncer::UpdateResponseDataList* updates)
  83. : updates_(updates) {
  84. DCHECK(updates_);
  85. }
  86. LazySyncIdToGuidMapInUpdates(const LazySyncIdToGuidMapInUpdates&) = delete;
  87. LazySyncIdToGuidMapInUpdates& operator=(const LazySyncIdToGuidMapInUpdates&) =
  88. delete;
  89. base::StringPiece GetGuidForSyncId(const std::string& sync_id) {
  90. InitializeIfNeeded();
  91. auto it = sync_id_to_guid_map_.find(sync_id);
  92. if (it == sync_id_to_guid_map_.end()) {
  93. return base::StringPiece();
  94. }
  95. return it->second;
  96. }
  97. private:
  98. void InitializeIfNeeded() {
  99. if (initialized_) {
  100. return;
  101. }
  102. initialized_ = true;
  103. for (const syncer::UpdateResponseData& update : *updates_) {
  104. base::StringPiece guid = GetGuidForEntity(update.entity);
  105. if (!update.entity.id.empty() && !guid.empty()) {
  106. const bool success =
  107. sync_id_to_guid_map_.emplace(update.entity.id, guid).second;
  108. DCHECK(success);
  109. }
  110. }
  111. }
  112. const raw_ptr<const syncer::UpdateResponseDataList> updates_;
  113. bool initialized_ = false;
  114. std::
  115. unordered_map<base::StringPiece, base::StringPiece, base::StringPieceHash>
  116. sync_id_to_guid_map_;
  117. };
  118. base::GUID GetParentGuidForUpdate(
  119. const syncer::UpdateResponseData& update,
  120. const SyncedBookmarkTracker* tracker,
  121. LazySyncIdToGuidMapInUpdates* sync_id_to_guid_map_in_updates) {
  122. DCHECK(tracker);
  123. DCHECK(sync_id_to_guid_map_in_updates);
  124. if (update.entity.legacy_parent_id.empty()) {
  125. // Without the |SyncEntity.parent_id| field set, there is no information
  126. // available to determine the parent and/or its GUID.
  127. return base::GUID();
  128. }
  129. // If a tracker is available, i.e. initial sync already done, it may know
  130. // parent's GUID already.
  131. base::GUID guid = TryGetParentGuidFromTracker(tracker, update);
  132. if (guid.is_valid()) {
  133. return guid;
  134. }
  135. // Otherwise, fall back to checking if the parent is included in the full list
  136. // of updates, represented here by |sync_id_to_guid_map_in_updates|. This
  137. // codepath is most crucial for initial sync, where |tracker| is empty, but is
  138. // also useful for non-initial sync, if the same incoming batch creates both
  139. // parent and child, none of which would be known by |tracker|.
  140. guid = base::GUID::ParseLowercase(
  141. sync_id_to_guid_map_in_updates->GetGuidForSyncId(
  142. update.entity.legacy_parent_id));
  143. if (guid.is_valid()) {
  144. return guid;
  145. }
  146. // At this point the parent's GUID couldn't be determined, but actually
  147. // the |SyncEntity.parent_id| was non-empty. The update will be ignored
  148. // regardless, but to avoid behavioral differences in UMA metrics
  149. // Sync.ProblematicServerSideBookmarks[DuringMerge], a fake parent GUID is
  150. // used here, which is known to never match an existing entity.
  151. guid = base::GUID::ParseLowercase(kInvalidParentGuid);
  152. DCHECK(guid.is_valid());
  153. DCHECK(!tracker->GetEntityForGUID(guid));
  154. return guid;
  155. }
  156. // Same as PopulateParentGuidInSpecifics(), but |tracker| must not be null.
  157. void PopulateParentGuidInSpecificsWithTracker(
  158. const SyncedBookmarkTracker* tracker,
  159. syncer::UpdateResponseDataList* updates) {
  160. DCHECK(tracker);
  161. DCHECK(updates);
  162. LazySyncIdToGuidMapInUpdates sync_id_to_guid_map(updates);
  163. for (syncer::UpdateResponseData& update : *updates) {
  164. // Only legacy data, without the parent GUID in specifics populated,
  165. // requires work. This also excludes tombstones and permanent folders.
  166. if (!NeedsParentGuidInSpecifics(update)) {
  167. // No work needed.
  168. continue;
  169. }
  170. const base::GUID guid =
  171. GetParentGuidForUpdate(update, tracker, &sync_id_to_guid_map);
  172. if (guid.is_valid()) {
  173. update.entity.specifics.mutable_bookmark()->set_parent_guid(
  174. guid.AsLowercaseString());
  175. }
  176. }
  177. }
  178. } // namespace
  179. void PopulateParentGuidInSpecifics(const SyncedBookmarkTracker* tracker,
  180. syncer::UpdateResponseDataList* updates) {
  181. DCHECK(updates);
  182. if (tracker) {
  183. // The code in this file assumes permanent folders are tracked in
  184. // SyncedBookmarkTracker. Since this is prone to change in the future, the
  185. // DCHECK below is added to avoid subtle bugs, without relying exclusively
  186. // on integration tests that exercise legacy data..
  187. DCHECK(tracker->GetEntityForGUID(base::GUID::ParseLowercase(
  188. bookmarks::BookmarkNode::kBookmarkBarNodeGuid)));
  189. DCHECK(tracker->GetEntityForGUID(base::GUID::ParseLowercase(
  190. bookmarks::BookmarkNode::kOtherBookmarksNodeGuid)));
  191. DCHECK(tracker->GetEntityForGUID(base::GUID::ParseLowercase(
  192. bookmarks::BookmarkNode::kMobileBookmarksNodeGuid)));
  193. PopulateParentGuidInSpecificsWithTracker(tracker, updates);
  194. return;
  195. }
  196. // No tracker provided, so use an empty tracker instead where all lookups will
  197. // fail.
  198. std::unique_ptr<SyncedBookmarkTracker> empty_tracker =
  199. SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
  200. PopulateParentGuidInSpecificsWithTracker(empty_tracker.get(), updates);
  201. }
  202. std::string GetGuidForSyncIdInUpdatesForTesting( // IN-TEST
  203. const syncer::UpdateResponseDataList& updates,
  204. const std::string& sync_id) {
  205. LazySyncIdToGuidMapInUpdates sync_id_to_guid_map(&updates);
  206. return std::string(sync_id_to_guid_map.GetGuidForSyncId(sync_id));
  207. }
  208. } // namespace sync_bookmarks