0
Files
src/components/browser_sync/sync_client_utils.cc
Victor Hugo Vianna Silva ef3a2d1717 Revert "Reland "Clean up kSyncEnableModelTypeLocalDataBatchUploaders kill switch""
This reverts commit 161ebdcab7.
It's not a pure revert because several instances of "ModelType"
have since been renamed to DataType. Patchset 2 does the necessary
changes. The flag name is unchanged, so it matches the one in the
M128 binary.
Reason for revert: crashes, see https://crbug.com/360304897.

Original change's description:
> Reland "Clean up kSyncEnableModelTypeLocalDataBatchUploaders kill switch"
>
> This is a reland of commit 68af3d5462.
> The original CL only got reverted to allow implementing
> crrev.com/c/5735294 in the flag-disabled code path too.
> This CL relands the original CL as is, modulo solving conflicts.
> Patchset 1 is not a pure reupload of the change because there were
> modify/delete conflicts and gerrit doesn't handle those.
>
> Original CL description:
>
> Deletes the kill switch used in crrev.com/c/5720897, along with the
> old implementations of the batch upload APIs in sync_client_utils.cc.
>
> OBSOLETE_HISTOGRAM[Sync.BatchUpload.Requests2]=Replaced by Sync.BatchUpload.Requests3.
>
> Bug: 40072432
> Change-Id: I162060916c1c70ef2e270e49521f9c3ea80cb4e2
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5729938
> Commit-Queue: Victor Vianna <victorvianna@google.com>
> Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
> Reviewed-by: Mikel Astiz <mastiz@chromium.org>
> Cr-Original-Commit-Position: refs/heads/main@{#1331610}
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5748841
> Cr-Commit-Position: refs/heads/main@{#1335956}

Bug: 40072432
Bug: 360304897
Change-Id: Ia2ce0a9d17df02be4e09bbc214e992ead936272d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5797349
Reviewed-by: Marc Treib <treib@chromium.org>
Commit-Queue: Victor Vianna <victorvianna@google.com>
Cr-Commit-Position: refs/heads/main@{#1344143}
2024-08-20 14:59:45 +00:00

484 lines
19 KiB
C++

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/browser_sync/sync_client_utils.h"
#include <algorithm>
#include <functional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/barrier_closure.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_store/password_store_consumer.h"
#include "components/password_manager/core/browser/password_store/password_store_interface.h"
#include "components/reading_list/core/dual_reading_list_model.h"
#include "components/sync/base/data_type_histogram.h"
#include "components/sync/base/features.h"
#include "components/sync/service/local_data_description.h"
#include "components/sync_bookmarks/bookmark_model_view.h"
#include "components/sync_bookmarks/local_bookmark_model_merger.h"
#include "components/url_formatter/elide_url.h"
#include "ui/base/models/tree_node_iterator.h"
namespace browser_sync {
namespace {
const syncer::DataTypeSet kSupportedTypes = {
syncer::PASSWORDS, syncer::BOOKMARKS, syncer::READING_LIST};
template <typename ContainerT, typename F>
syncer::LocalDataDescription CreateLocalDataDescription(ContainerT&& items,
F&& url_extractor) {
std::vector<GURL> urls;
std::ranges::transform(items, std::back_inserter(urls), url_extractor);
return syncer::LocalDataDescription(std::move(urls));
}
// Returns urls of all the bookmarks which can be moved to the account store,
// i.e. it does not include folders nor managed bookmarks.
std::vector<GURL> GetAllUserBookmarksExcludingFolders(
sync_bookmarks::BookmarkModelView* model) {
std::vector<GURL> bookmarked_urls;
ui::TreeNodeIterator<const bookmarks::BookmarkNode> iterator(
model->root_node());
while (iterator.has_next()) {
const bookmarks::BookmarkNode* const node = iterator.Next();
// Skip folders and non-syncable nodes (e.g. managed bookmarks).
if (node->is_url() && model->IsNodeSyncable(node)) {
bookmarked_urls.push_back(node->url());
}
}
return bookmarked_urls;
}
// Returns the latest of a password form's last used time, last update time and
// creation time.
base::Time GetLatestOfTimeLastUsedOrModifiedOrCreated(
const password_manager::PasswordForm& form) {
return std::max(
{form.date_last_used, form.date_password_modified, form.date_created});
}
// Some of the services required for data migrations might not exist (e.g.
// disabled for some reason) or may not have initialized (initialization is
// ongoing or failed). In these cases, a sensible fallback is to exclude the
// affected types. This function returns the set of types that are usable,
// i.e. their dependent services are available and ready.
syncer::DataTypeSet FilterUsableTypes(
syncer::DataTypeSet types,
password_manager::PasswordStoreInterface* profile_password_store,
password_manager::PasswordStoreInterface* account_password_store,
sync_bookmarks::BookmarkModelView* local_bookmark_model_view,
sync_bookmarks::BookmarkModelView* account_bookmark_model_view,
reading_list::DualReadingListModel* reading_list_model) {
if (!profile_password_store || !account_password_store ||
!account_password_store->IsAbleToSavePasswords()) {
types.Remove(syncer::PASSWORDS);
}
if (!local_bookmark_model_view || !account_bookmark_model_view ||
!local_bookmark_model_view->loaded() ||
!account_bookmark_model_view->loaded()) {
types.Remove(syncer::BOOKMARKS);
}
if (!reading_list_model || !reading_list_model->loaded()) {
types.Remove(syncer::READING_LIST);
}
return types;
}
} // namespace
// A class to represent individual local data query requests.
class LocalDataQueryHelper::LocalDataQueryRequest
: public password_manager::PasswordStoreConsumer {
public:
LocalDataQueryRequest(
LocalDataQueryHelper* helper,
syncer::DataTypeSet types,
base::OnceCallback<void(
std::map<syncer::DataType, syncer::LocalDataDescription>)> callback)
: helper_(helper), types_(base::Intersection(types, kSupportedTypes)) {
if (types_ != types) {
DVLOG(1) << "Only PASSWORDS, BOOKMARKS and READING_LIST are supported.";
}
// Note that the BarrierClosure is initialized after all other data members.
// If `types_` is empty, the closure will get triggered right away and if
// the callback uses any of the other data members, this can lead to
// unexpected behaviour (see crbug.com/1482218).
barrier_callback_ = base::BarrierClosure(
types_.size(),
base::BindOnce(&LocalDataQueryHelper::OnRequestComplete,
base::Unretained(helper_), base::Unretained(this),
std::move(callback)));
}
~LocalDataQueryRequest() override = default;
// This runs the query for the requested data types.
void Run() {
// If no supported type is requested, return early. The BarrierClosure would
// have already called the result callback.
if (types_.empty()) {
return;
}
if (types_.Has(syncer::PASSWORDS)) {
CHECK(helper_->profile_password_store_);
helper_->profile_password_store_->GetAutofillableLogins(
weak_ptr_factory_.GetWeakPtr());
}
if (types_.Has(syncer::BOOKMARKS)) {
CHECK(helper_->local_bookmark_model_view_);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&LocalDataQueryHelper::LocalDataQueryRequest::FetchLocalBookmarks,
weak_ptr_factory_.GetWeakPtr()));
}
if (types_.Has(syncer::READING_LIST)) {
CHECK(helper_->dual_reading_list_model_);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&LocalDataQueryHelper::LocalDataQueryRequest::
FetchLocalReadingList,
weak_ptr_factory_.GetWeakPtr()));
}
}
// PasswordStoreConsumer implementation.
void OnGetPasswordStoreResults(
std::vector<std::unique_ptr<password_manager::PasswordForm>>
local_passwords) override {
result_.emplace(
syncer::PASSWORDS,
CreateLocalDataDescription(
std::move(local_passwords),
[](const std::unique_ptr<password_manager::PasswordForm>&
password_form) { return password_form->url; }));
// Trigger the barrier closure.
barrier_callback_.Run();
}
void FetchLocalBookmarks() {
std::vector<GURL> bookmarked_urls = GetAllUserBookmarksExcludingFolders(
helper_->local_bookmark_model_view_.get());
result_.emplace(syncer::BOOKMARKS,
CreateLocalDataDescription(std::move(bookmarked_urls),
std::identity()));
// Trigger the barrier closure.
barrier_callback_.Run();
}
void FetchLocalReadingList() {
base::flat_set<GURL> keys =
helper_->dual_reading_list_model_->GetKeysThatNeedUploadToSyncServer();
result_.emplace(
syncer::READING_LIST,
CreateLocalDataDescription(std::move(keys), std::identity()));
// Trigger the barrier closure.
barrier_callback_.Run();
}
const std::map<syncer::DataType, syncer::LocalDataDescription>& result()
const {
CHECK(result_.size() == types_.size()) << "Request is still on-going.";
return result_;
}
private:
raw_ptr<LocalDataQueryHelper> helper_;
syncer::DataTypeSet types_;
// A barrier closure to trigger the callback once the local data for all the
// types has been fetched.
base::RepeatingClosure barrier_callback_;
std::map<syncer::DataType, syncer::LocalDataDescription> result_;
base::WeakPtrFactory<LocalDataQueryRequest> weak_ptr_factory_{this};
};
LocalDataQueryHelper::LocalDataQueryHelper(
password_manager::PasswordStoreInterface* profile_password_store,
password_manager::PasswordStoreInterface* account_password_store,
bookmarks::BookmarkModel* bookmark_model,
reading_list::DualReadingListModel* dual_reading_list_model)
: profile_password_store_(profile_password_store),
account_password_store_(account_password_store),
local_bookmark_model_view_(
bookmark_model
? std::make_unique<
sync_bookmarks::BookmarkModelViewUsingLocalOrSyncableNodes>(
bookmark_model)
: nullptr),
account_bookmark_model_view_(
bookmark_model && base::FeatureList::IsEnabled(
syncer::kSyncEnableBookmarksInTransportMode)
? std::make_unique<
sync_bookmarks::BookmarkModelViewUsingAccountNodes>(
bookmark_model)
: nullptr),
dual_reading_list_model_(dual_reading_list_model) {}
LocalDataQueryHelper::~LocalDataQueryHelper() = default;
void LocalDataQueryHelper::Run(
syncer::DataTypeSet types,
base::OnceCallback<void(
std::map<syncer::DataType, syncer::LocalDataDescription>)> callback) {
syncer::DataTypeSet usable_types = FilterUsableTypes(
types, profile_password_store_, account_password_store_,
local_bookmark_model_view_.get(), account_bookmark_model_view_.get(),
dual_reading_list_model_);
// Create a request to query info about local data of all `usable_types`.
std::unique_ptr<LocalDataQueryRequest> request_ptr =
std::make_unique<LocalDataQueryRequest>(this, usable_types,
std::move(callback));
LocalDataQueryRequest& request = *request_ptr;
request_list_.push_back(std::move(request_ptr));
request.Run();
}
void LocalDataQueryHelper::OnRequestComplete(
LocalDataQueryRequest* request,
base::OnceCallback<void(
std::map<syncer::DataType, syncer::LocalDataDescription>)> callback) {
// Execute the callback.
std::move(callback).Run(request->result());
// Remove the request from the list of ongoing requests.
request_list_.remove_if(
[&](const std::unique_ptr<LocalDataQueryRequest>& item) {
return item.get() == request;
});
}
// A class to represent individual local data migration requests.
class LocalDataMigrationHelper::LocalDataMigrationRequest
: public password_manager::PasswordStoreConsumer {
public:
LocalDataMigrationRequest(LocalDataMigrationHelper* helper,
syncer::DataTypeSet types)
: helper_(helper), types_(base::Intersection(types, kSupportedTypes)) {
if (types_ != types) {
DVLOG(1) << "Only PASSWORDS, BOOKMARKS and READING_LIST are supported.";
}
}
~LocalDataMigrationRequest() override = default;
const syncer::DataTypeSet& types() const { return types_; }
// This runs the query for the requested data types.
void Run() {
for (syncer::DataType type : types_) {
base::UmaHistogramEnumeration("Sync.BatchUpload.Requests2",
syncer::DataTypeHistogramValue(type));
}
if (types_.Has(syncer::BOOKMARKS)) {
CHECK(helper_->local_bookmark_model_view_);
CHECK(helper_->account_bookmark_model_view_);
// Guard against absence of account bookmarks. For example, this can
// happen if the initial download hasn't completed.
if (helper_->account_bookmark_model_view_->bookmark_bar_node() !=
nullptr) {
// Merge all local bookmarks into the account bookmark model.
sync_bookmarks::LocalBookmarkModelMerger(
helper_->local_bookmark_model_view_.get(),
helper_->account_bookmark_model_view_.get())
.Merge();
// Remove all bookmarks from the local model.
helper_->local_bookmark_model_view_->RemoveAllSyncableNodes();
}
}
if (types_.Has(syncer::READING_LIST)) {
CHECK(helper_->dual_reading_list_model_);
helper_->dual_reading_list_model_->MarkAllForUploadToSyncServerIfNeeded();
}
if (!types_.Has(syncer::PASSWORDS)) {
// All above are synchronous, so if PASSWORDS isn't requested, the
// operation completes immediately.
helper_->OnRequestComplete(this);
// Note that at this point `this` is destroyed, as the function above
// causes LocalDataMigrationHelper to delete the request.
return;
}
CHECK(helper_->profile_password_store_);
CHECK(helper_->account_password_store_);
// Fetch the local and the account passwords.
helper_->profile_password_store_->GetAutofillableLogins(
weak_ptr_factory_.GetWeakPtr());
helper_->account_password_store_->GetAutofillableLogins(
weak_ptr_factory_.GetWeakPtr());
}
// PasswordStoreConsumer implementation.
void OnGetPasswordStoreResults(
std::vector<std::unique_ptr<password_manager::PasswordForm>>
local_passwords) override {
// Not implemented since OnGetPasswordStoreResultsFrom is already
// overridden.
NOTIMPLEMENTED();
}
void OnGetPasswordStoreResultsFrom(
password_manager::PasswordStoreInterface* store,
std::vector<std::unique_ptr<password_manager::PasswordForm>> results)
override {
if (store == helper_->profile_password_store_) {
profile_passwords_ = std::move(results);
} else {
account_passwords_ = std::move(results);
}
// Proceed once results from both the stores are available.
if (!profile_passwords_.has_value() || !account_passwords_.has_value()) {
return;
}
// Sort `account_passwords_`.
// Comparator for sorting passwords using their unique key.
auto comparator =
[](const std::unique_ptr<password_manager::PasswordForm>& lhs,
const std::unique_ptr<password_manager::PasswordForm>& rhs) {
return password_manager::PasswordFormUniqueKey(*lhs) <
password_manager::PasswordFormUniqueKey(*rhs);
};
base::ranges::sort(*account_passwords_, comparator);
int moved_passwords_counter = 0;
// Iterate over all local passwords and add to account store if required.
for (std::unique_ptr<password_manager::PasswordForm>& profile_password :
*profile_passwords_) {
auto it = base::ranges::lower_bound(*account_passwords_, profile_password,
comparator);
// Check if a password with the same key exists in the account store.
// If it doesn't, that means there exists no conflicting password.
// If it does and the password value differs, keep the most recently used
// password.
if (it == account_passwords_->end() ||
!password_manager::ArePasswordFormUniqueKeysEqual(
*(*it), *profile_password)) {
// No conflicting password exists in the account store. Add the same to
// the account store.
helper_->account_password_store_->AddLogin(*profile_password);
++moved_passwords_counter;
} else if ((*it)->password_value != profile_password->password_value &&
// Check if `profile_password` was more recently used or
// updated.
// In some cases, last used time and last update time can be
// null (see crbug.com/1483452). Thus, the max of {last used
// time, last updated time, creation time} is used to decide
// which password wins.
GetLatestOfTimeLastUsedOrModifiedOrCreated(**it) <
GetLatestOfTimeLastUsedOrModifiedOrCreated(
*profile_password)) {
// `profile_password` is newer. Add it to the account store.
helper_->account_password_store_->UpdateLogin(*profile_password);
++moved_passwords_counter;
}
// Remove `profile_password` from the local store.
helper_->profile_password_store_->RemoveLogin(FROM_HERE,
*profile_password);
}
// Log number of passwords moved to account.
base::UmaHistogramCounts1M("Sync.PasswordsBatchUpload.Count",
moved_passwords_counter);
helper_->OnRequestComplete(this);
// Note that at this point `this` is destroyed, as the function above causes
// LocalDataMigrationHelper to delete the request.
}
private:
raw_ptr<LocalDataMigrationHelper> helper_;
const syncer::DataTypeSet types_;
std::optional<std::vector<std::unique_ptr<password_manager::PasswordForm>>>
profile_passwords_;
std::optional<std::vector<std::unique_ptr<password_manager::PasswordForm>>>
account_passwords_;
base::WeakPtrFactory<LocalDataMigrationRequest> weak_ptr_factory_{this};
};
LocalDataMigrationHelper::LocalDataMigrationHelper(
password_manager::PasswordStoreInterface* profile_password_store,
password_manager::PasswordStoreInterface* account_password_store,
bookmarks::BookmarkModel* bookmark_model,
reading_list::DualReadingListModel* dual_reading_list_model)
: profile_password_store_(profile_password_store),
account_password_store_(account_password_store),
local_bookmark_model_view_(
bookmark_model
? std::make_unique<
sync_bookmarks::BookmarkModelViewUsingLocalOrSyncableNodes>(
bookmark_model)
: nullptr),
account_bookmark_model_view_(
bookmark_model && base::FeatureList::IsEnabled(
syncer::kSyncEnableBookmarksInTransportMode)
? std::make_unique<
sync_bookmarks::BookmarkModelViewUsingAccountNodes>(
bookmark_model)
: nullptr),
dual_reading_list_model_(dual_reading_list_model) {}
LocalDataMigrationHelper::~LocalDataMigrationHelper() = default;
void LocalDataMigrationHelper::Run(syncer::DataTypeSet types) {
syncer::DataTypeSet usable_types = FilterUsableTypes(
types, profile_password_store_, account_password_store_,
local_bookmark_model_view_.get(), account_bookmark_model_view_.get(),
dual_reading_list_model_);
// Create a request to move all local data of all `usable_types` to the
// account store.
std::unique_ptr<LocalDataMigrationRequest> request_ptr =
std::make_unique<LocalDataMigrationRequest>(this, usable_types);
LocalDataMigrationRequest& request = *request_ptr;
request_list_.push_back(std::move(request_ptr));
request.Run();
}
syncer::DataTypeSet LocalDataMigrationHelper::GetTypesWithOngoingMigrations()
const {
syncer::DataTypeSet types;
for (const auto& request : request_list_) {
types.PutAll(request->types());
}
return types;
}
void LocalDataMigrationHelper::OnRequestComplete(
LocalDataMigrationRequest* request) {
// Remove from the list of ongoing requests.
request_list_.remove_if(
[&](const std::unique_ptr<LocalDataMigrationRequest>& item) {
return item.get() == request;
});
}
} // namespace browser_sync