bookmark_model_observer_impl.cc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. // Copyright 2018 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/bookmark_model_observer_impl.h"
  5. #include <utility>
  6. #include "base/guid.h"
  7. #include "base/no_destructor.h"
  8. #include "components/bookmarks/browser/bookmark_model.h"
  9. #include "components/bookmarks/browser/bookmark_node.h"
  10. #include "components/sync/base/hash_util.h"
  11. #include "components/sync/base/unique_position.h"
  12. #include "components/sync/engine/commit_and_get_updates_types.h"
  13. #include "components/sync/protocol/entity_metadata.pb.h"
  14. #include "components/sync/protocol/entity_specifics.pb.h"
  15. #include "components/sync_bookmarks/bookmark_specifics_conversions.h"
  16. #include "components/sync_bookmarks/synced_bookmark_tracker_entity.h"
  17. #include "third_party/abseil-cpp/absl/types/variant.h"
  18. namespace sync_bookmarks {
  19. namespace {
  20. // A helper wrapper used to compare UniquePosition with positions before the
  21. // first and after the last elements.
  22. class UniquePositionWrapper {
  23. public:
  24. static UniquePositionWrapper Min() {
  25. return UniquePositionWrapper(MinUniquePosition{});
  26. }
  27. static UniquePositionWrapper Max() {
  28. return UniquePositionWrapper(MaxUniquePosition{});
  29. }
  30. // |unique_position| must be valid.
  31. static UniquePositionWrapper ForValidUniquePosition(
  32. syncer::UniquePosition unique_position) {
  33. DCHECK(unique_position.IsValid());
  34. return UniquePositionWrapper(std::move(unique_position));
  35. }
  36. UniquePositionWrapper(UniquePositionWrapper&&) = default;
  37. UniquePositionWrapper& operator=(UniquePositionWrapper&&) = default;
  38. // Returns valid UniquePosition or invalid one for Min() and Max().
  39. const syncer::UniquePosition& GetUniquePosition() const {
  40. static const base::NoDestructor<syncer::UniquePosition>
  41. kEmptyUniquePosition;
  42. if (HoldsUniquePosition()) {
  43. return absl::get<syncer::UniquePosition>(value_);
  44. }
  45. return *kEmptyUniquePosition;
  46. }
  47. bool LessThan(const UniquePositionWrapper& other) const {
  48. if (value_.index() != other.value_.index()) {
  49. return value_.index() < other.value_.index();
  50. }
  51. if (!HoldsUniquePosition()) {
  52. // Both arguments are MinUniquePosition or MaxUniquePosition, in both
  53. // cases they are equal.
  54. return false;
  55. }
  56. return GetUniquePosition().LessThan(other.GetUniquePosition());
  57. }
  58. private:
  59. struct MinUniquePosition {};
  60. struct MaxUniquePosition {};
  61. explicit UniquePositionWrapper(absl::variant<MinUniquePosition,
  62. syncer::UniquePosition,
  63. MaxUniquePosition> value)
  64. : value_(std::move(value)) {}
  65. bool HoldsUniquePosition() const {
  66. return absl::holds_alternative<syncer::UniquePosition>(value_);
  67. }
  68. // The order is used to compare positions.
  69. absl::variant<MinUniquePosition, syncer::UniquePosition, MaxUniquePosition>
  70. value_;
  71. };
  72. } // namespace
  73. BookmarkModelObserverImpl::BookmarkModelObserverImpl(
  74. const base::RepeatingClosure& nudge_for_commit_closure,
  75. base::OnceClosure on_bookmark_model_being_deleted_closure,
  76. SyncedBookmarkTracker* bookmark_tracker)
  77. : bookmark_tracker_(bookmark_tracker),
  78. nudge_for_commit_closure_(nudge_for_commit_closure),
  79. on_bookmark_model_being_deleted_closure_(
  80. std::move(on_bookmark_model_being_deleted_closure)) {
  81. DCHECK(bookmark_tracker_);
  82. }
  83. BookmarkModelObserverImpl::~BookmarkModelObserverImpl() = default;
  84. void BookmarkModelObserverImpl::BookmarkModelLoaded(
  85. bookmarks::BookmarkModel* model,
  86. bool ids_reassigned) {
  87. // This class isn't responsible for any loading-related logic.
  88. }
  89. void BookmarkModelObserverImpl::BookmarkModelBeingDeleted(
  90. bookmarks::BookmarkModel* model) {
  91. std::move(on_bookmark_model_being_deleted_closure_).Run();
  92. }
  93. void BookmarkModelObserverImpl::BookmarkNodeMoved(
  94. bookmarks::BookmarkModel* model,
  95. const bookmarks::BookmarkNode* old_parent,
  96. size_t old_index,
  97. const bookmarks::BookmarkNode* new_parent,
  98. size_t new_index) {
  99. const bookmarks::BookmarkNode* node = new_parent->children()[new_index].get();
  100. // We shouldn't see changes to the top-level nodes.
  101. DCHECK(!model->is_permanent_node(node));
  102. if (!model->client()->CanSyncNode(node)) {
  103. return;
  104. }
  105. const SyncedBookmarkTrackerEntity* entity =
  106. bookmark_tracker_->GetEntityForBookmarkNode(node);
  107. DCHECK(entity);
  108. const std::string& sync_id = entity->metadata().server_id();
  109. const base::Time modification_time = base::Time::Now();
  110. const syncer::UniquePosition unique_position =
  111. ComputePosition(*new_parent, new_index, sync_id);
  112. sync_pb::EntitySpecifics specifics =
  113. CreateSpecificsFromBookmarkNode(node, model, unique_position.ToProto(),
  114. /*force_favicon_load=*/true);
  115. bookmark_tracker_->Update(entity, entity->metadata().server_version(),
  116. modification_time, specifics);
  117. // Mark the entity that it needs to be committed.
  118. bookmark_tracker_->IncrementSequenceNumber(entity);
  119. nudge_for_commit_closure_.Run();
  120. bookmark_tracker_->CheckAllNodesTracked(model);
  121. }
  122. void BookmarkModelObserverImpl::BookmarkNodeAdded(
  123. bookmarks::BookmarkModel* model,
  124. const bookmarks::BookmarkNode* parent,
  125. size_t index) {
  126. const bookmarks::BookmarkNode* node = parent->children()[index].get();
  127. if (!model->client()->CanSyncNode(node)) {
  128. return;
  129. }
  130. const SyncedBookmarkTrackerEntity* parent_entity =
  131. bookmark_tracker_->GetEntityForBookmarkNode(parent);
  132. DCHECK(parent_entity);
  133. const syncer::UniquePosition unique_position =
  134. ComputePosition(*parent, index, node->guid().AsLowercaseString());
  135. sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
  136. node, model, unique_position.ToProto(), /*force_favicon_load=*/true);
  137. // It is possible that a created bookmark was restored after deletion and
  138. // the tombstone was not committed yet. In that case the existing entity
  139. // should be updated.
  140. const SyncedBookmarkTrackerEntity* entity =
  141. bookmark_tracker_->GetEntityForGUID(node->guid());
  142. const base::Time creation_time = base::Time::Now();
  143. if (entity) {
  144. // If there is a tracked entity with the same client tag hash (effectively
  145. // the same bookmark GUID), it must be a tombstone. Otherwise it means
  146. // the bookmark model contains to bookmarks with the same GUID.
  147. DCHECK(!entity->bookmark_node()) << "Added bookmark with duplicate GUID";
  148. bookmark_tracker_->UndeleteTombstoneForBookmarkNode(entity, node);
  149. bookmark_tracker_->Update(entity, entity->metadata().server_version(),
  150. creation_time, specifics);
  151. } else {
  152. entity = bookmark_tracker_->Add(node, node->guid().AsLowercaseString(),
  153. syncer::kUncommittedVersion, creation_time,
  154. specifics);
  155. }
  156. // Mark the entity that it needs to be committed.
  157. bookmark_tracker_->IncrementSequenceNumber(entity);
  158. nudge_for_commit_closure_.Run();
  159. // Do not check if all nodes are tracked because it's still possible that some
  160. // nodes are untracked, e.g. if current node has been just restored and its
  161. // children will be added soon.
  162. }
  163. void BookmarkModelObserverImpl::OnWillRemoveBookmarks(
  164. bookmarks::BookmarkModel* model,
  165. const bookmarks::BookmarkNode* parent,
  166. size_t old_index,
  167. const bookmarks::BookmarkNode* node) {
  168. if (!model->client()->CanSyncNode(node)) {
  169. return;
  170. }
  171. bookmark_tracker_->CheckAllNodesTracked(model);
  172. ProcessDelete(node);
  173. nudge_for_commit_closure_.Run();
  174. }
  175. void BookmarkModelObserverImpl::BookmarkNodeRemoved(
  176. bookmarks::BookmarkModel* model,
  177. const bookmarks::BookmarkNode* parent,
  178. size_t old_index,
  179. const bookmarks::BookmarkNode* node,
  180. const std::set<GURL>& removed_urls) {
  181. // All the work should have already been done in OnWillRemoveBookmarks.
  182. DCHECK(bookmark_tracker_->GetEntityForBookmarkNode(node) == nullptr);
  183. bookmark_tracker_->CheckAllNodesTracked(model);
  184. }
  185. void BookmarkModelObserverImpl::OnWillRemoveAllUserBookmarks(
  186. bookmarks::BookmarkModel* model) {
  187. bookmark_tracker_->CheckAllNodesTracked(model);
  188. const bookmarks::BookmarkNode* root_node = model->root_node();
  189. for (const auto& permanent_node : root_node->children()) {
  190. for (const auto& child : permanent_node->children()) {
  191. if (model->client()->CanSyncNode(child.get())) {
  192. ProcessDelete(child.get());
  193. }
  194. }
  195. }
  196. nudge_for_commit_closure_.Run();
  197. }
  198. void BookmarkModelObserverImpl::BookmarkAllUserNodesRemoved(
  199. bookmarks::BookmarkModel* model,
  200. const std::set<GURL>& removed_urls) {
  201. // All the work should have already been done in OnWillRemoveAllUserBookmarks.
  202. bookmark_tracker_->CheckAllNodesTracked(model);
  203. }
  204. void BookmarkModelObserverImpl::BookmarkNodeChanged(
  205. bookmarks::BookmarkModel* model,
  206. const bookmarks::BookmarkNode* node) {
  207. if (!model->client()->CanSyncNode(node)) {
  208. return;
  209. }
  210. // We shouldn't see changes to the top-level nodes.
  211. DCHECK(!model->is_permanent_node(node));
  212. const SyncedBookmarkTrackerEntity* entity =
  213. bookmark_tracker_->GetEntityForBookmarkNode(node);
  214. if (!entity) {
  215. // If the node hasn't been added to the tracker yet, we do nothing. It will
  216. // be added later. It's how BookmarkModel currently notifies observers, if
  217. // further changes are triggered *during* observer notification. Consider
  218. // the following scenario:
  219. // 1. New bookmark added.
  220. // 2. BookmarkModel notifies all the observers about the new node.
  221. // 3. One observer A get's notified before us.
  222. // 4. Observer A decided to update the bookmark node.
  223. // 5. BookmarkModel notifies all observers about the update.
  224. // 6. We received the notification about the update before the creation.
  225. // 7. We will get the notification about the addition later and then we can
  226. // start tracking the node.
  227. return;
  228. }
  229. sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
  230. node, model, entity->metadata().unique_position(),
  231. /*force_favicon_load=*/true);
  232. ProcessUpdate(entity, specifics);
  233. }
  234. void BookmarkModelObserverImpl::BookmarkMetaInfoChanged(
  235. bookmarks::BookmarkModel* model,
  236. const bookmarks::BookmarkNode* node) {
  237. BookmarkNodeChanged(model, node);
  238. }
  239. void BookmarkModelObserverImpl::BookmarkNodeFaviconChanged(
  240. bookmarks::BookmarkModel* model,
  241. const bookmarks::BookmarkNode* node) {
  242. if (!model->client()->CanSyncNode(node)) {
  243. return;
  244. }
  245. // We shouldn't see changes to the top-level nodes.
  246. DCHECK(!model->is_permanent_node(node));
  247. // Ignore favicons that are being loaded.
  248. if (!node->is_favicon_loaded()) {
  249. // Subtle way to trigger a load of the favicon. This very same function will
  250. // be notified when the favicon gets loaded (read from HistoryService and
  251. // cached in RAM within BookmarkModel).
  252. model->GetFavicon(node);
  253. return;
  254. }
  255. const SyncedBookmarkTrackerEntity* entity =
  256. bookmark_tracker_->GetEntityForBookmarkNode(node);
  257. if (!entity) {
  258. // This should be practically unreachable but in theory it's possible that a
  259. // favicon changes *during* the creation of a bookmark (by another
  260. // observer). See analogous codepath in BookmarkNodeChanged().
  261. return;
  262. }
  263. const sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
  264. node, model, entity->metadata().unique_position(),
  265. /*force_favicon_load=*/false);
  266. // TODO(crbug.com/1094825): implement |base_specifics_hash| similar to
  267. // ClientTagBasedModelTypeProcessor.
  268. if (!entity->MatchesFaviconHash(specifics.bookmark().favicon())) {
  269. ProcessUpdate(entity, specifics);
  270. return;
  271. }
  272. // The favicon content didn't actually change, which means this event is
  273. // almost certainly the result of favicon loading having completed.
  274. if (entity->IsUnsynced()) {
  275. // Nudge for commit once favicon is loaded. This is needed in case when
  276. // unsynced entity was skipped while building commit requests (since favicon
  277. // wasn't loaded).
  278. nudge_for_commit_closure_.Run();
  279. }
  280. }
  281. void BookmarkModelObserverImpl::BookmarkNodeChildrenReordered(
  282. bookmarks::BookmarkModel* model,
  283. const bookmarks::BookmarkNode* node) {
  284. if (!model->client()->CanSyncNode(node)) {
  285. return;
  286. }
  287. if (node->children().size() <= 1) {
  288. // There is no real change in the order of |node|'s children.
  289. return;
  290. }
  291. // The given node's children got reordered, all the corresponding sync nodes
  292. // need to be reordered. The code is optimized to detect move of only one
  293. // bookmark (which is the case on Android platform). There are 2 main cases:
  294. // a bookmark moved to left or to right. Moving a bookmark to the first and
  295. // last positions are two more special cases. The algorithm iterates over each
  296. // bookmark and compares it to the left and right nodes to determine whether
  297. // it's ordered or not.
  298. //
  299. // Each digit below represents bookmark's original position.
  300. //
  301. // Moving a bookmark to the left: 0123456 -> 0612345.
  302. // When processing '6', its unique position is greater than both left and
  303. // right nodes.
  304. //
  305. // Moving a bookmark to the right: 0123456 -> 0234516.
  306. // When processing '1', its unique position is less than both left and right
  307. // nodes.
  308. //
  309. // Note that in both cases left node is less than right node. This condition
  310. // is checked when iterating over bookmarks and if it's violated, the
  311. // algorithm falls back to generating positions for all the following nodes.
  312. //
  313. // For example, if two nodes are moved to one place: 0123456 -> 0156234 (nodes
  314. // '5' and '6' are moved together). In this case, 0156 will remain and when
  315. // processing '2', algorithm will fall back to generating unique positions for
  316. // nodes '2', '3' and '4'. It will be detected by comparing the next node '3'
  317. // with the previous '6'.
  318. // Store |cur| outside of the loop to prevent parsing UniquePosition twice.
  319. UniquePositionWrapper cur = UniquePositionWrapper::ForValidUniquePosition(
  320. GetUniquePositionForNode(node->children().front().get()));
  321. UniquePositionWrapper prev = UniquePositionWrapper::Min();
  322. for (size_t current_index = 0; current_index < node->children().size();
  323. ++current_index) {
  324. UniquePositionWrapper next = UniquePositionWrapper::Max();
  325. if (current_index + 1 < node->children().size()) {
  326. next = UniquePositionWrapper::ForValidUniquePosition(
  327. GetUniquePositionForNode(node->children()[current_index + 1].get()));
  328. }
  329. // |prev| is the last ordered node. Compare |cur| and |next| with it to
  330. // decide whether current node needs to be updated. Consider the following
  331. // cases: 0. |prev| < |cur| < |next|: all elements are ordered.
  332. // 1. |cur| < |prev| < |next|: update current node and put it between |prev|
  333. // and |next|.
  334. // 2. |cur| < |next| < |prev|: both |cur| and |next| are out of order, fall
  335. // back to simple approach.
  336. // 3. |next| < |cur| < |prev|: same as #2.
  337. // 4. |prev| < |next| < |cur|: update current node and put it between |prev|
  338. // and |next|.
  339. // 5. |next| < |prev| < |cur|: consider current node ordered, |next| will be
  340. // updated on the next step.
  341. //
  342. // In the following code only cases where current node needs to be updated
  343. // are considered (#0 and #5 are omitted because there is nothing to do).
  344. bool update_current_position = false;
  345. if (cur.LessThan(prev)) {
  346. // |cur| < |prev|, cases #1, #2 and #3.
  347. if (next.LessThan(prev)) {
  348. // There are two consecutive nodes which both are out of order (#2, #3):
  349. // |prev| > |cur| and |prev| > |next|.
  350. // It means that more than one bookmark has been reordered, fall back to
  351. // generating unique positions for all the remaining children.
  352. //
  353. // |current_index| is always not 0 because |prev| cannot be
  354. // UniquePositionWrapper::Min() if |next| < |prev|.
  355. DCHECK_GT(current_index, 0u);
  356. UpdateAllUniquePositionsStartingAt(node, model, current_index);
  357. break;
  358. }
  359. update_current_position = true;
  360. } else if (next.LessThan(cur) && prev.LessThan(next)) {
  361. // |prev| < |next| < |cur| (case #4).
  362. update_current_position = true;
  363. }
  364. if (update_current_position) {
  365. cur = UniquePositionWrapper::ForValidUniquePosition(
  366. UpdateUniquePositionForNode(node->children()[current_index].get(),
  367. model, prev.GetUniquePosition(),
  368. next.GetUniquePosition()));
  369. }
  370. DCHECK(prev.LessThan(cur));
  371. prev = std::move(cur);
  372. cur = std::move(next);
  373. }
  374. nudge_for_commit_closure_.Run();
  375. }
  376. syncer::UniquePosition BookmarkModelObserverImpl::ComputePosition(
  377. const bookmarks::BookmarkNode& parent,
  378. size_t index,
  379. const std::string& sync_id) {
  380. const std::string& suffix = syncer::GenerateSyncableBookmarkHash(
  381. bookmark_tracker_->model_type_state().cache_guid(), sync_id);
  382. DCHECK(!parent.children().empty());
  383. const SyncedBookmarkTrackerEntity* predecessor_entity = nullptr;
  384. const SyncedBookmarkTrackerEntity* successor_entity = nullptr;
  385. // Look for the first tracked predecessor.
  386. for (auto i = parent.children().crend() - index;
  387. i != parent.children().crend(); ++i) {
  388. const bookmarks::BookmarkNode* predecessor_node = i->get();
  389. predecessor_entity =
  390. bookmark_tracker_->GetEntityForBookmarkNode(predecessor_node);
  391. if (predecessor_entity) {
  392. break;
  393. }
  394. }
  395. // Look for the first tracked successor.
  396. for (auto i = parent.children().cbegin() + index + 1;
  397. i != parent.children().cend(); ++i) {
  398. const bookmarks::BookmarkNode* successor_node = i->get();
  399. successor_entity =
  400. bookmark_tracker_->GetEntityForBookmarkNode(successor_node);
  401. if (successor_entity) {
  402. break;
  403. }
  404. }
  405. if (!predecessor_entity && !successor_entity) {
  406. // No tracked siblings.
  407. return syncer::UniquePosition::InitialPosition(suffix);
  408. }
  409. if (!predecessor_entity && successor_entity) {
  410. // No predecessor, insert before the successor.
  411. return syncer::UniquePosition::Before(
  412. syncer::UniquePosition::FromProto(
  413. successor_entity->metadata().unique_position()),
  414. suffix);
  415. }
  416. if (predecessor_entity && !successor_entity) {
  417. // No successor, insert after the predecessor
  418. return syncer::UniquePosition::After(
  419. syncer::UniquePosition::FromProto(
  420. predecessor_entity->metadata().unique_position()),
  421. suffix);
  422. }
  423. // Both predecessor and successor, insert in the middle.
  424. return syncer::UniquePosition::Between(
  425. syncer::UniquePosition::FromProto(
  426. predecessor_entity->metadata().unique_position()),
  427. syncer::UniquePosition::FromProto(
  428. successor_entity->metadata().unique_position()),
  429. suffix);
  430. }
  431. void BookmarkModelObserverImpl::ProcessUpdate(
  432. const SyncedBookmarkTrackerEntity* entity,
  433. const sync_pb::EntitySpecifics& specifics) {
  434. DCHECK(entity);
  435. // Data should be committed to the server only if there is an actual change,
  436. // determined here by comparing hashes.
  437. if (entity->MatchesSpecificsHash(specifics)) {
  438. // Specifics haven't actually changed, so the local change can be ignored.
  439. return;
  440. }
  441. bookmark_tracker_->Update(entity, entity->metadata().server_version(),
  442. /*modification_time=*/base::Time::Now(), specifics);
  443. // Mark the entity that it needs to be committed.
  444. bookmark_tracker_->IncrementSequenceNumber(entity);
  445. nudge_for_commit_closure_.Run();
  446. }
  447. void BookmarkModelObserverImpl::ProcessDelete(
  448. const bookmarks::BookmarkNode* node) {
  449. // If not a leaf node, process all children first.
  450. for (const auto& child : node->children()) {
  451. ProcessDelete(child.get());
  452. }
  453. // Process the current node.
  454. const SyncedBookmarkTrackerEntity* entity =
  455. bookmark_tracker_->GetEntityForBookmarkNode(node);
  456. // Shouldn't try to delete untracked entities.
  457. DCHECK(entity);
  458. // If the entity hasn't been committed and doesn't have an inflight commit
  459. // request, simply remove it from the tracker.
  460. if (entity->metadata().server_version() == syncer::kUncommittedVersion &&
  461. !entity->commit_may_have_started()) {
  462. bookmark_tracker_->Remove(entity);
  463. return;
  464. }
  465. bookmark_tracker_->MarkDeleted(entity);
  466. // Mark the entity that it needs to be committed.
  467. bookmark_tracker_->IncrementSequenceNumber(entity);
  468. }
  469. syncer::UniquePosition BookmarkModelObserverImpl::GetUniquePositionForNode(
  470. const bookmarks::BookmarkNode* node) const {
  471. DCHECK(bookmark_tracker_);
  472. DCHECK(node);
  473. const SyncedBookmarkTrackerEntity* entity =
  474. bookmark_tracker_->GetEntityForBookmarkNode(node);
  475. DCHECK(entity);
  476. return syncer::UniquePosition::FromProto(
  477. entity->metadata().unique_position());
  478. }
  479. syncer::UniquePosition BookmarkModelObserverImpl::UpdateUniquePositionForNode(
  480. const bookmarks::BookmarkNode* node,
  481. bookmarks::BookmarkModel* bookmark_model,
  482. const syncer::UniquePosition& prev,
  483. const syncer::UniquePosition& next) {
  484. DCHECK(bookmark_tracker_);
  485. DCHECK(node);
  486. DCHECK(bookmark_model);
  487. const SyncedBookmarkTrackerEntity* entity =
  488. bookmark_tracker_->GetEntityForBookmarkNode(node);
  489. DCHECK(entity);
  490. const std::string suffix = syncer::GenerateSyncableBookmarkHash(
  491. bookmark_tracker_->model_type_state().cache_guid(),
  492. entity->metadata().server_id());
  493. const base::Time modification_time = base::Time::Now();
  494. syncer::UniquePosition new_unique_position;
  495. if (prev.IsValid() && next.IsValid()) {
  496. new_unique_position = syncer::UniquePosition::Between(prev, next, suffix);
  497. } else if (prev.IsValid()) {
  498. new_unique_position = syncer::UniquePosition::After(prev, suffix);
  499. } else {
  500. new_unique_position = syncer::UniquePosition::Before(next, suffix);
  501. }
  502. sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
  503. node, bookmark_model, new_unique_position.ToProto(),
  504. /*force_favicon_load=*/true);
  505. bookmark_tracker_->Update(entity, entity->metadata().server_version(),
  506. modification_time, specifics);
  507. // Mark the entity that it needs to be committed.
  508. bookmark_tracker_->IncrementSequenceNumber(entity);
  509. return new_unique_position;
  510. }
  511. void BookmarkModelObserverImpl::UpdateAllUniquePositionsStartingAt(
  512. const bookmarks::BookmarkNode* parent,
  513. bookmarks::BookmarkModel* bookmark_model,
  514. size_t start_index) {
  515. DCHECK_GT(start_index, 0u);
  516. DCHECK_LT(start_index, parent->children().size());
  517. syncer::UniquePosition prev =
  518. GetUniquePositionForNode(parent->children()[start_index - 1].get());
  519. for (size_t current_index = start_index;
  520. current_index < parent->children().size(); ++current_index) {
  521. // Right position is unknown because it will also be updated.
  522. prev = UpdateUniquePositionForNode(parent->children()[current_index].get(),
  523. bookmark_model, prev,
  524. /*next=*/syncer::UniquePosition());
  525. }
  526. }
  527. } // namespace sync_bookmarks