From e33f23f1de15eb1f65e6b41f2bcb92fe4bae431e Mon Sep 17 00:00:00 2001 From: Boris Sazonov <bsazonov@google.com> Date: Mon, 15 May 2023 10:21:10 +0000 Subject: [PATCH] [Bookmark] Clear account BookmarkModel on sign-out BookmarkModelTypeProcessor::OnSyncStopping is called with CLEAR_METADATA upon sign-out. After this CL, BookmarkModelTypeProcessor can be configured to call RemoveAllUserBookmarks at that moment. Bug: 1444662 Change-Id: Ida7bba5e80940bd9ad81e80dc1062f283b8fab12 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4527935 Reviewed-by: Mikel Astiz <mastiz@chromium.org> Commit-Queue: Boris Sazonov <bsazonov@chromium.org> Cr-Commit-Position: refs/heads/main@{#1143978} --- .../sync/bookmark_sync_service_factory.cc | 3 +- .../bookmark_model_type_processor.cc | 41 +++++++----- .../bookmark_model_type_processor.h | 28 ++++++--- .../bookmark_model_type_processor_unittest.cc | 62 ++++++++++++++++--- .../sync_bookmarks/bookmark_sync_service.cc | 7 ++- .../sync_bookmarks/bookmark_sync_service.h | 10 ++- .../account_bookmark_sync_service_factory.cc | 4 +- ..._syncable_bookmark_sync_service_factory.cc | 4 +- 8 files changed, 116 insertions(+), 43 deletions(-) diff --git a/chrome/browser/sync/bookmark_sync_service_factory.cc b/chrome/browser/sync/bookmark_sync_service_factory.cc index 3a0a187a39c40..548fb4b69b76e 100644 --- a/chrome/browser/sync/bookmark_sync_service_factory.cc +++ b/chrome/browser/sync/bookmark_sync_service_factory.cc @@ -38,5 +38,6 @@ KeyedService* BookmarkSyncServiceFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { Profile* profile = Profile::FromBrowserContext(context); return new sync_bookmarks::BookmarkSyncService( - BookmarkUndoServiceFactory::GetForProfileIfExists(profile)); + BookmarkUndoServiceFactory::GetForProfileIfExists(profile), + /*wipe_model_on_stopping_sync_with_clear_data=*/false); } diff --git a/components/sync_bookmarks/bookmark_model_type_processor.cc b/components/sync_bookmarks/bookmark_model_type_processor.cc index ab84151c1d8d8..cf9d32dd70643 100644 --- a/components/sync_bookmarks/bookmark_model_type_processor.cc +++ b/components/sync_bookmarks/bookmark_model_type_processor.cc @@ -51,7 +51,7 @@ constexpr size_t kDefaultMaxBookmarksTillSyncEnabled = 100000; class ScopedRemoteUpdateBookmarks { public: - // |bookmark_model|, |bookmark_undo_service| and |observer| must not be null + // `bookmark_model`, `bookmark_undo_service` and `observer` must not be null // and must outlive this object. ScopedRemoteUpdateBookmarks(bookmarks::BookmarkModel* bookmark_model, BookmarkUndoService* bookmark_undo_service, @@ -123,8 +123,11 @@ size_t CountSyncableBookmarksFromModel(bookmarks::BookmarkModel* model) { } // namespace BookmarkModelTypeProcessor::BookmarkModelTypeProcessor( - BookmarkUndoService* bookmark_undo_service) + BookmarkUndoService* bookmark_undo_service, + bool wipe_model_on_stopping_sync_with_clear_data) : bookmark_undo_service_(bookmark_undo_service), + wipe_model_on_stopping_sync_with_clear_data_( + wipe_model_on_stopping_sync_with_clear_data), max_bookmarks_till_sync_enabled_(kDefaultMaxBookmarksTillSyncEnabled) {} BookmarkModelTypeProcessor::~BookmarkModelTypeProcessor() { @@ -141,7 +144,7 @@ void BookmarkModelTypeProcessor::ConnectSync( worker_ = std::move(worker); - // |bookmark_tracker_| is instantiated only after initial sync is done. + // `bookmark_tracker_` is instantiated only after initial sync is done. if (bookmark_tracker_) { NudgeForCommitIfNeeded(); } @@ -164,7 +167,7 @@ void BookmarkModelTypeProcessor::GetLocalChanges( GetLocalChangesCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Processor should never connect if - // |last_initial_merge_remote_updates_exceeded_limit_| is set. + // `last_initial_merge_remote_updates_exceeded_limit_` is set. DCHECK(!last_initial_merge_remote_updates_exceeded_limit_); BookmarkLocalChangesBuilder builder(bookmark_tracker_.get(), bookmark_model_); std::move(callback).Run(builder.BuildCommitRequests(max_entries)); @@ -176,7 +179,7 @@ void BookmarkModelTypeProcessor::OnCommitCompleted( const syncer::FailedCommitResponseDataList& error_response_list) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // |error_response_list| is ignored, because all errors are treated as + // `error_response_list` is ignored, because all errors are treated as // transient and the processor with eventually retry. for (const syncer::CommitResponseData& response : committed_response_list) { const SyncedBookmarkTrackerEntity* entity = @@ -205,10 +208,10 @@ void BookmarkModelTypeProcessor::OnUpdateReceived( DCHECK(syncer::IsInitialSyncDone(model_type_state.initial_sync_state())); DCHECK(start_callback_.is_null()); // Processor should never connect if - // |last_initial_merge_remote_updates_exceeded_limit_| is set. + // `last_initial_merge_remote_updates_exceeded_limit_` is set. DCHECK(!last_initial_merge_remote_updates_exceeded_limit_); - // TODO(crbug.com/1356900): validate incoming updates, e.g. |gc_directive| + // TODO(crbug.com/1356900): validate incoming updates, e.g. `gc_directive` // must be empty for Bookmarks. syncer::LogUpdatesReceivedByProcessorHistogram( @@ -285,7 +288,7 @@ bool BookmarkModelTypeProcessor::IsConnectedForTest() const { std::string BookmarkModelTypeProcessor::EncodeSyncMetadata() const { std::string metadata_str; if (bookmark_tracker_) { - // |last_initial_merge_remote_updates_exceeded_limit_| is only set in error + // `last_initial_merge_remote_updates_exceeded_limit_` is only set in error // cases where the tracker would not be initialized. DCHECK(!last_initial_merge_remote_updates_exceeded_limit_); @@ -343,7 +346,7 @@ void BookmarkModelTypeProcessor::ModelReadyToSync( syncer::kSyncEnforceBookmarksCountLimit)) { // Report error if remote updates fetched last time during initial merge // exceeded limit. Note that here we are only setting - // |last_initial_merge_remote_updates_exceeded_limit_|, the actual error + // `last_initial_merge_remote_updates_exceeded_limit_`, the actual error // would be reported in ConnectIfReady(). last_initial_merge_remote_updates_exceeded_limit_ = true; } else { @@ -354,8 +357,8 @@ void BookmarkModelTypeProcessor::ModelReadyToSync( if (bookmark_tracker_) { StartTrackingMetadata(); } else if (!metadata_str.empty()) { - // Even if the field |last_initial_merge_remote_updates_exceeded_limit| is - // set and the feature toggle |kSyncEnforceBookmarksCountLimit| not + // Even if the field `last_initial_merge_remote_updates_exceeded_limit` is + // set and the feature toggle `kSyncEnforceBookmarksCountLimit` not // enabled, making the metadata_str non-empty, scheduling a save shouldn't // cause any problem. DLOG(WARNING) @@ -398,7 +401,7 @@ void BookmarkModelTypeProcessor::OnSyncStarting( StartCallback start_callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(start_callback); - // |favicon_service_| should have been set by now. + // `favicon_service_` should have been set by now. DCHECK(favicon_service_); DVLOG(1) << "Sync is starting for Bookmarks"; @@ -428,7 +431,7 @@ void BookmarkModelTypeProcessor::ConnectIfReady() { // Report error if remote updates fetched last time during initial merge // exceeded limit. if (last_initial_merge_remote_updates_exceeded_limit_) { - // |last_initial_merge_remote_updates_exceeded_limit_| is only set in error + // `last_initial_merge_remote_updates_exceeded_limit_` is only set in error // case and thus tracker should be empty. DCHECK(!bookmark_tracker_); start_callback_.Reset(); @@ -510,6 +513,14 @@ void BookmarkModelTypeProcessor::OnSyncStopping( StopTrackingMetadataAndResetTracker(); } last_initial_merge_remote_updates_exceeded_limit_ = false; + if (wipe_model_on_stopping_sync_with_clear_data_) { + // `CLEAR_METADATA` indicates sync is permanently disabled. Since + // `wipe_model_on_stopping_sync_with_clear_data_` is `true`, the + // lifetime of local data (bookmarks) is coupled with sync metadata's, + // which means disabling sync requires that bookmarks in local storage + // are deleted. + bookmark_model_->RemoveAllUserBookmarks(); + } schedule_save_closure_.Run(); break; } @@ -524,7 +535,7 @@ void BookmarkModelTypeProcessor::NudgeForCommitIfNeeded() { DCHECK(bookmark_tracker_); // Issue error and stop sync if the number of local bookmarks exceed limit. - // If |error_handler_| is not set, the check is ignored because this gets + // If `error_handler_` is not set, the check is ignored because this gets // re-evaluated in ConnectIfReady(). if (error_handler_ && bookmark_tracker_->TrackedBookmarksCount() > @@ -564,7 +575,7 @@ void BookmarkModelTypeProcessor::OnInitialUpdateReceived( TRACE_EVENT0("sync", "BookmarkModelTypeProcessor::OnInitialUpdateReceived"); - // |updates| can contain an additional root folder. The server may or may not + // `updates` can contain an additional root folder. The server may or may not // deliver a root node - it is not guaranteed, but this works as an // approximated safeguard. const size_t max_initial_updates_count = max_bookmarks_till_sync_enabled_ + 1; diff --git a/components/sync_bookmarks/bookmark_model_type_processor.h b/components/sync_bookmarks/bookmark_model_type_processor.h index 1416e086a7ae3..d14469f0989ad 100644 --- a/components/sync_bookmarks/bookmark_model_type_processor.h +++ b/components/sync_bookmarks/bookmark_model_type_processor.h @@ -35,9 +35,12 @@ class BookmarkModelObserverImpl; class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, public syncer::ModelTypeControllerDelegate { public: - // |bookmark_undo_service| must not be nullptr and must outlive this object. - explicit BookmarkModelTypeProcessor( - BookmarkUndoService* bookmark_undo_service); + // `bookmark_undo_service` must not be nullptr and must outlive this object. + // If `wipe_model_on_stopping_sync_with_clear_data` is `true`, then calling + // `OnSyncStopping` with `CLEAR_METADATA` will trigger the removal of all user + // bookmarks from the corresponding `BookmarkModel`. + BookmarkModelTypeProcessor(BookmarkUndoService* bookmark_undo_service, + bool wipe_model_on_stopping_sync_with_clear_data); BookmarkModelTypeProcessor(const BookmarkModelTypeProcessor&) = delete; BookmarkModelTypeProcessor& operator=(const BookmarkModelTypeProcessor&) = @@ -78,10 +81,10 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, std::string EncodeSyncMetadata() const; // It mainly decodes a BookmarkModelMetadata proto serialized in - // |metadata_str|, and uses it to fill in the tracker and the model type state - // objects. |model| must not be null and must outlive this object. It is used + // `metadata_str`, and uses it to fill in the tracker and the model type state + // objects. `model` must not be null and must outlive this object. It is used // to the retrieve the local node ids, and is stored in the processor to be - // used for further model operations. |schedule_save_closure| is a repeating + // used for further model operations. `schedule_save_closure` is a repeating // closure used to schedule a save of the bookmark model together with the // metadata. void ModelReadyToSync(const std::string& metadata_str, @@ -90,7 +93,7 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, // Sets the favicon service used when processing remote updates. It must be // called before the processor is ready to receive remote updates, and hence - // before OnSyncStarting() is called. |favicon_service| must not be null. + // before OnSyncStarting() is called. `favicon_service` must not be null. void SetFaviconService(favicon::FaviconService* favicon_service); // Returns the estimate of dynamically allocated memory in bytes. @@ -136,9 +139,9 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, void StopTrackingMetadataAndResetTracker(); // Creates a DictionaryValue for local and remote debugging information about - // |node| and appends it to |all_nodes|. It does the same for child nodes - // recursively. |index| is the index of |node| within its parent. |index| - // could computed from |node|, however it's much cheaper to pass from outside + // `node` and appends it to `all_nodes`. It does the same for child nodes + // recursively. `index` is the index of `node` within its parent. `index` + // could computed from `node`, however it's much cheaper to pass from outside // since we iterate over child nodes already in the calling sites. void AppendNodeAndChildrenForDebugging(const bookmarks::BookmarkNode* node, int index, @@ -163,6 +166,11 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, // Used to suspend bookmark undo when processing remote changes. const raw_ptr<BookmarkUndoService, DanglingUntriaged> bookmark_undo_service_; + // Controls whether bookmarks should be wiped when sync is stopped. Contains + // `true` for Account `BookmarkModel` and `false` for LocalOrSyncable + // `BookmarkModel`. + const bool wipe_model_on_stopping_sync_with_clear_data_; + // The callback used to schedule the persistence of bookmark model as well as // the metadata to a file during which latest metadata should also be pulled // via EncodeSyncMetadata. Processor should invoke it upon changes in the diff --git a/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc b/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc index c6e353274d8a6..f8950df2b0c38 100644 --- a/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc +++ b/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc @@ -50,6 +50,7 @@ using testing::IsEmpty; using testing::IsNull; using testing::NiceMock; using testing::NotNull; +using testing::Pointer; using testing::UnorderedElementsAre; const char kBookmarkBarTag[] = "bookmark_bar"; @@ -244,7 +245,8 @@ class BookmarkModelTypeProcessorTest : public testing::Test { public: BookmarkModelTypeProcessorTest() : processor_(std::make_unique<BookmarkModelTypeProcessor>( - &bookmark_undo_service_)), + &bookmark_undo_service_, + /*wipe_model_on_stopping_sync_with_clear_data=*/false)), bookmark_model_(bookmarks::TestBookmarkClient::CreateModel()) { processor_->SetFaviconService(&favicon_service_); } @@ -306,9 +308,10 @@ class BookmarkModelTypeProcessorTest : public testing::Test { } // Simulate browser restart. - void ResetModelTypeProcessor() { - processor_ = - std::make_unique<BookmarkModelTypeProcessor>(&bookmark_undo_service_); + void ResetModelTypeProcessor( + bool wipe_model_on_stopping_sync_with_clear_data = false) { + processor_ = std::make_unique<BookmarkModelTypeProcessor>( + &bookmark_undo_service_, wipe_model_on_stopping_sync_with_clear_data); processor_->SetFaviconService(&favicon_service_); } @@ -517,7 +520,9 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldDecodeSyncMetadata) { CreateNodeMetadata(bookmarknode, kNodeId); // Create a new processor and init it with the metadata str. - BookmarkModelTypeProcessor new_processor(bookmark_undo_service()); + BookmarkModelTypeProcessor new_processor( + bookmark_undo_service(), + /*wipe_model_on_stopping_sync_with_clear_data=*/false); std::string metadata_str; model_metadata.SerializeToString(&metadata_str); @@ -546,7 +551,9 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldDecodeEncodedSyncMetadata) { SimulateModelReadyToSyncWithInitialSyncDone(); // Create a new processor and init it with the same metadata str. - BookmarkModelTypeProcessor new_processor(bookmark_undo_service()); + BookmarkModelTypeProcessor new_processor( + bookmark_undo_service(), + /*wipe_model_on_stopping_sync_with_clear_data=*/false); new_processor.ModelReadyToSync(processor()->EncodeSyncMetadata(), base::DoNothing(), bookmark_model()); @@ -579,7 +586,9 @@ TEST_F(BookmarkModelTypeProcessorTest, bookmark_metadata->mutable_metadata()->set_server_id(kBookmarkBarId); // Create a new processor and init it with the metadata str. - BookmarkModelTypeProcessor new_processor(bookmark_undo_service()); + BookmarkModelTypeProcessor new_processor( + bookmark_undo_service(), + /*wipe_model_on_stopping_sync_with_clear_data=*/false); // A save should be scheduled. NiceMock<base::MockCallback<base::RepeatingClosure>> @@ -606,7 +615,9 @@ TEST_F(BookmarkModelTypeProcessorTest, /*server_id=*/kBookmarkBarId); // Create a new processor and init it with the metadata str. - BookmarkModelTypeProcessor new_processor(bookmark_undo_service()); + BookmarkModelTypeProcessor new_processor( + bookmark_undo_service(), + /*wipe_model_on_stopping_sync_with_clear_data=*/false); // A save should be scheduled. NiceMock<base::MockCallback<base::RepeatingClosure>> @@ -1573,6 +1584,41 @@ TEST_F(BookmarkModelTypeProcessorTest, "Sync.ClearMetadataWhileStopped.ImmediateClear", 0); } +TEST_F(BookmarkModelTypeProcessorTest, + ShouldWipeBookmarksIfStoppedWithClearMetadata) { + ResetModelTypeProcessor(/*wipe_model_on_stopping_sync_with_clear_data=*/true); + + const GURL kUrl("http://www.example.com"); + bookmark_model()->AddURL(bookmark_model()->bookmark_bar_node(), /*index=*/0, + u"foo", kUrl); + const bookmarks::BookmarkNode* folder = bookmark_model()->AddFolder( + bookmark_model()->mobile_node(), /*index=*/0, u"folder"); + bookmark_model()->AddURL(folder, /*index=*/0, u"bar", kUrl); + + SimulateModelReadyToSyncWithInitialSyncDone(); + SimulateOnSyncStarting(); + ASSERT_FALSE(bookmark_model()->HasNoUserCreatedBookmarksOrFolders()); + + processor()->OnSyncStopping(syncer::CLEAR_METADATA); + EXPECT_TRUE(bookmark_model()->HasNoUserCreatedBookmarksOrFolders()); +} + +TEST_F(BookmarkModelTypeProcessorTest, + ShouldNotWipeBookmarksIfStoppedWithKeepMetadata) { + ResetModelTypeProcessor(/*wipe_model_on_stopping_sync_with_clear_data=*/true); + + const GURL kUrl("http://www.example.com"); + const bookmarks::BookmarkNode* node = bookmark_model()->AddURL( + bookmark_model()->mobile_node(), /*index=*/0, u"foo", kUrl); + + SimulateModelReadyToSyncWithInitialSyncDone(); + SimulateOnSyncStarting(); + + processor()->OnSyncStopping(syncer::KEEP_METADATA); + EXPECT_THAT(bookmark_model()->mobile_node()->children(), + ElementsAre(Pointer(Eq(node)))); +} + TEST_F(BookmarkModelTypeProcessorTest, ShouldNotClearMetadataWhileStoppedWithoutMetadataInitially) { SimulateModelReadyToSyncWithoutLocalMetadata(); diff --git a/components/sync_bookmarks/bookmark_sync_service.cc b/components/sync_bookmarks/bookmark_sync_service.cc index afeafd5ee9730..0105eddd797c4 100644 --- a/components/sync_bookmarks/bookmark_sync_service.cc +++ b/components/sync_bookmarks/bookmark_sync_service.cc @@ -10,8 +10,11 @@ namespace sync_bookmarks { BookmarkSyncService::BookmarkSyncService( - BookmarkUndoService* bookmark_undo_service) - : bookmark_model_type_processor_(bookmark_undo_service) {} + BookmarkUndoService* bookmark_undo_service, + bool wipe_model_on_stopping_sync_with_clear_data) + : bookmark_model_type_processor_( + bookmark_undo_service, + wipe_model_on_stopping_sync_with_clear_data) {} BookmarkSyncService::~BookmarkSyncService() = default; diff --git a/components/sync_bookmarks/bookmark_sync_service.h b/components/sync_bookmarks/bookmark_sync_service.h index 7544836ad52e3..94edb706c298b 100644 --- a/components/sync_bookmarks/bookmark_sync_service.h +++ b/components/sync_bookmarks/bookmark_sync_service.h @@ -34,8 +34,12 @@ class BookmarkModelTypeProcessor; // This service owns the BookmarkModelTypeProcessor. class BookmarkSyncService : public KeyedService { public: - // |bookmark_undo_service| must not be null and must outlive this object. - explicit BookmarkSyncService(BookmarkUndoService* bookmark_undo_service); + // If `wipe_model_on_stopping_sync_with_clear_data` is `true`, then the + // `bookmark_undo_service` must not be null and must outlive this object. + // lifetime of bookmarks in the associated storage is coupled with sync + // metadata's, so disabling sync will delete bookmarks in the storage. + BookmarkSyncService(BookmarkUndoService* bookmark_undo_service, + bool wipe_model_on_stopping_sync_with_clear_data); BookmarkSyncService(const BookmarkSyncService&) = delete; BookmarkSyncService& operator=(const BookmarkSyncService&) = delete; @@ -51,7 +55,7 @@ class BookmarkSyncService : public KeyedService { bookmarks::BookmarkModel* model); // Returns the ModelTypeControllerDelegate for syncer::BOOKMARKS. - // |favicon_service| is the favicon service used when processing updates in + // `favicon_service` is the favicon service used when processing updates in // the underlying processor. It could have been a separate a setter in // BookmarkSyncService instead of passing it as a parameter to // GetBookmarkSyncControllerDelegate(). However, this would incur the risk of diff --git a/ios/chrome/browser/bookmarks/account_bookmark_sync_service_factory.cc b/ios/chrome/browser/bookmarks/account_bookmark_sync_service_factory.cc index 177d7f7214bc6..b71073e5f4d89 100644 --- a/ios/chrome/browser/bookmarks/account_bookmark_sync_service_factory.cc +++ b/ios/chrome/browser/bookmarks/account_bookmark_sync_service_factory.cc @@ -51,8 +51,8 @@ AccountBookmarkSyncServiceFactory::BuildServiceInstanceFor( ChromeBrowserState::FromBrowserState(context); std::unique_ptr<sync_bookmarks::BookmarkSyncService> bookmark_sync_service( new sync_bookmarks::BookmarkSyncService( - BookmarkUndoServiceFactory::GetForBrowserStateIfExists( - browser_state))); + BookmarkUndoServiceFactory::GetForBrowserStateIfExists(browser_state), + /*wipe_model_on_stopping_sync_with_clear_data=*/true)); return bookmark_sync_service; } diff --git a/ios/chrome/browser/bookmarks/local_or_syncable_bookmark_sync_service_factory.cc b/ios/chrome/browser/bookmarks/local_or_syncable_bookmark_sync_service_factory.cc index fb2ec3f8f604d..06a67adaaeddf 100644 --- a/ios/chrome/browser/bookmarks/local_or_syncable_bookmark_sync_service_factory.cc +++ b/ios/chrome/browser/bookmarks/local_or_syncable_bookmark_sync_service_factory.cc @@ -46,8 +46,8 @@ LocalOrSyncableBookmarkSyncServiceFactory::BuildServiceInstanceFor( ChromeBrowserState::FromBrowserState(context); std::unique_ptr<sync_bookmarks::BookmarkSyncService> bookmark_sync_service( new sync_bookmarks::BookmarkSyncService( - BookmarkUndoServiceFactory::GetForBrowserStateIfExists( - browser_state))); + BookmarkUndoServiceFactory::GetForBrowserStateIfExists(browser_state), + /*wipe_model_on_stopping_sync_with_clear_data=*/false)); return bookmark_sync_service; }