From 2fa4515000e9a5d2e0d7f465064e5cc345e51c96 Mon Sep 17 00:00:00 2001 From: Pavel Beloborodov <73575789+boocmp@users.noreply.github.com> Date: Fri, 16 May 2025 19:45:37 +0700 Subject: [PATCH] Fixed extensions import (#29084) * Fixed crash during import from multiple profiles. * Fixed state check of the source extension. --- browser/importer/BUILD.gn | 20 ++ .../brave_external_process_importer_host.cc | 5 + .../brave_external_process_importer_host.h | 1 + .../importer/brave_importer_browsertest.cc | 184 ++++++++++++++++++ browser/importer/extensions_import_helpers.cc | 1 + common/importer/chrome_importer_utils.cc | 16 +- test/BUILD.gn | 1 + 7 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 browser/importer/brave_importer_browsertest.cc diff --git a/browser/importer/BUILD.gn b/browser/importer/BUILD.gn index 2494a8cf4b0..badb99626c2 100644 --- a/browser/importer/BUILD.gn +++ b/browser/importer/BUILD.gn @@ -34,4 +34,24 @@ if (!is_android) { "//testing/gtest", ] } + + source_set("browser_tests") { + testonly = true + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] + + sources = [ "brave_importer_browsertest.cc" ] + + deps = [ + "//base", + "//brave/common/importer:importer", + "//chrome/browser/importer", + "//chrome/browser/ui", + "//chrome/common:constants", + "//chrome/common/importer", + "//chrome/test:test_support", + "//components/value_store:test_support", + "//content/test:test_support", + "//testing/gtest", + ] + } } diff --git a/browser/importer/brave_external_process_importer_host.cc b/browser/importer/brave_external_process_importer_host.cc index faea2af95e3..54f36246080 100644 --- a/browser/importer/brave_external_process_importer_host.cc +++ b/browser/importer/brave_external_process_importer_host.cc @@ -51,10 +51,14 @@ void BraveExternalProcessImporterHost::LaunchImportIfReady() { weak_ptr_factory_.GetWeakPtr())); return; } + if (!extensions_import_ready_) { + return; + } } #endif if (!do_not_launch_import_for_testing_) { + CHECK(!client_); ExternalProcessImporterHost::LaunchImportIfReady(); } else { NotifyImportEnded(); @@ -93,6 +97,7 @@ void BraveExternalProcessImporterHost::OnExtensionsImportReady(bool ready) { weak_ptr_factory_.GetWeakPtr()), IDS_EXTENSIONS_IMPORTER_LOCK_TITLE, IDS_EXTENSIONS_IMPORTER_LOCK_TEXT); } else { + extensions_import_ready_ = true; LaunchImportIfReady(); } } diff --git a/browser/importer/brave_external_process_importer_host.h b/browser/importer/brave_external_process_importer_host.h index 1cd7ed18a5e..b3da3210692 100644 --- a/browser/importer/brave_external_process_importer_host.h +++ b/browser/importer/brave_external_process_importer_host.h @@ -55,6 +55,7 @@ class BraveExternalProcessImporterHost : public ExternalProcessImporterHost { extensions_import::ExtensionImportStatus status); std::unique_ptr extensions_importer_; + bool extensions_import_ready_ = false; #endif bool do_not_launch_import_for_testing_ = false; diff --git a/browser/importer/brave_importer_browsertest.cc b/browser/importer/brave_importer_browsertest.cc new file mode 100644 index 00000000000..f275db827c5 --- /dev/null +++ b/browser/importer/brave_importer_browsertest.cc @@ -0,0 +1,184 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "base/files/file_util.h" +#include "base/memory/scoped_refptr.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/task/sequenced_task_runner.h" +#include "base/test/bind.h" +#include "brave/browser/importer/brave_external_process_importer_host.h" +#include "brave/browser/importer/extensions_import_helpers.h" +#include "brave/common/importer/chrome_importer_utils.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/importer/profile_writer.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/profiles/profile_test_util.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "components/value_store/test_value_store_factory.h" +#include "components/value_store/value_store.h" +#include "content/public/test/browser_test.h" +#include "extensions/browser/extension_registrar.h" +#include "extensions/browser/extension_system.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_builder.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +constexpr char kExtensionId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + +class TestObserver : public importer::ImporterProgressObserver { + public: + ~TestObserver() override = default; + + void ImportStarted() override {} + void ImportItemStarted(importer::ImportItem item) override {} + void ImportItemEnded(importer::ImportItem item) override {} + MOCK_METHOD(void, ImportEnded, (), (override)); +}; + +} // namespace + +class BraveImporterBrowserTest : public InProcessBrowserTest { + public: + Profile* CreateProfile() { + ProfileManager* profile_manager = g_browser_process->profile_manager(); + base::FilePath profile_path = + profile_manager->GenerateNextProfileDirectoryPath(); + return &profiles::testing::CreateProfileSync(profile_manager, profile_path); + } + + void NonBlockingDelay(base::TimeDelta delay) { + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, run_loop.QuitWhenIdleClosure(), delay); + run_loop.Run(); + } + + std::string ReadStore(Profile* profile) { + base::ScopedAllowBlockingForTesting allow_io; + const auto path = + profile->GetPath() + .Append(extensions::kLocalExtensionSettingsDirectoryName) + .AppendASCII(kExtensionId); + + if (!base::DirectoryExists(path)) { + return {}; + } + auto store_factory = + base::MakeRefCounted(path); + auto source_store = store_factory->CreateValueStore( + base::FilePath(extensions::kLocalExtensionSettingsDirectoryName), + kExtensionId); + auto setting = source_store->Get(); + if (!setting.status().ok()) { + return {}; + } + return *setting.PassSettings().FindString("id"); + } + + std::string ReadIndexedDB(Profile* profile) { + base::ScopedAllowBlockingForTesting allow_io; + + const auto indexeddb_path = + profile->GetPath() + .AppendASCII("IndexedDB") + .AppendASCII(base::StrCat( + {"chrome-extension_", kExtensionId, "_indexeddb.test"})) + .AppendASCII("test"); + std::string contents; + base::ReadFileToString(indexeddb_path, &contents); + return contents; + } + + void AddTestExtension(Profile* profile) { + scoped_refptr extension( + extensions::ExtensionBuilder("extension") + .AddFlags(extensions::Extension::FROM_WEBSTORE) + .SetID(kExtensionId) + .AddJSON(R"("manifest_version": 2, "version": "1.0.0")") + .SetLocation(extensions::mojom::ManifestLocation::kInternal) + .Build()); + extensions::ExtensionRegistrar::Get(profile)->AddExtension(extension); + extensions::ExtensionPrefs::Get(profile)->OnExtensionInstalled( + extension.get(), {}, {}, extensions::Extension::FROM_WEBSTORE, {}, + base::Value::Dict()); + + base::ScopedAllowBlockingForTesting allow_blocking; + const auto indexeddb_path = + profile->GetPath() + .AppendASCII("IndexedDB") + .AppendASCII(base::StrCat( + {"chrome-extension_", kExtensionId, "_indexeddb.test"})); + const auto local_store_path = + profile->GetPath() + .Append(extensions::kLocalExtensionSettingsDirectoryName) + .AppendASCII(kExtensionId); + + // Simulate pref data. + auto store_factory = + base::MakeRefCounted( + local_store_path); + auto source_store = store_factory->CreateValueStore( + base::FilePath(extensions::kLocalExtensionSettingsDirectoryName), + kExtensionId); + source_store->Set(value_store::ValueStore::DEFAULTS, "id", + base::Value(kExtensionId)); + + base::CreateDirectory(indexeddb_path); + base::WriteFile(indexeddb_path.AppendASCII("test"), "test"); + + // Wait for prefs are written on the disk. + while (!GetImportableChromeExtensionsList(profile->GetPath())) { + NonBlockingDelay(base::Milliseconds(10)); + } + } +}; + +IN_PROC_BROWSER_TEST_F(BraveImporterBrowserTest, ImportExtensions) { + auto* source_profile = CreateProfile(); + AddTestExtension(source_profile); + + for (int i = 0; i < 3; ++i) { + Profile* target = CreateProfile(); + + // Deletes itself. + auto* host = new BraveExternalProcessImporterHost; + + testing::NiceMock observer; + + base::RunLoop run_loop; + EXPECT_CALL(observer, ImportEnded()) + .WillOnce( + ::testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); + + host->set_observer(&observer); + + importer::SourceProfile source; + source.importer_type = importer::TYPE_CHROME; + source.source_path = source_profile->GetPath(); + + bool extension_imported = false; + extensions_import::ExtensionsImporter::GetExtensionInstallerForTesting() = + base::BindLambdaForTesting( + [&](const std::string& id) + -> extensions_import::ExtensionImportStatus { + EXPECT_EQ(id, kExtensionId); + extension_imported = true; + return extensions_import::ExtensionImportStatus::kOk; + }); + + host->StartImportSettings(source, target, importer::EXTENSIONS, + new ProfileWriter(target)); + run_loop.Run(); + EXPECT_TRUE(extension_imported); + EXPECT_EQ(kExtensionId, ReadStore(target)); + } +} diff --git a/browser/importer/extensions_import_helpers.cc b/browser/importer/extensions_import_helpers.cc index aca65d68c47..be3760554d4 100644 --- a/browser/importer/extensions_import_helpers.cc +++ b/browser/importer/extensions_import_helpers.cc @@ -146,6 +146,7 @@ bool ImportIndexedDBExtensionSettings(const std::string& extension_id, // Copy settings. source_enumerator.ForEach([&target_profile, &kIndexedDBDir](const base::FilePath& path) { + base::CreateDirectory(target_profile.AppendASCII(kIndexedDBDir)); base::CopyDirectory( path, target_profile.AppendASCII(kIndexedDBDir).Append(path.BaseName()), true); diff --git a/common/importer/chrome_importer_utils.cc b/common/importer/chrome_importer_utils.cc index f73e29024d4..b5374611d08 100644 --- a/common/importer/chrome_importer_utils.cc +++ b/common/importer/chrome_importer_utils.cc @@ -70,12 +70,22 @@ std::vector GetImportableListFromChromeExtensionsList( if (dict.FindBool("was_installed_by_default").value_or(true)) continue; - // `"state": 0` means disabled state - if (!dict.FindInt("state").value_or(false)) + const auto state = dict.FindInt("state"); + if (state.has_value()) { + // If `state` exists, probably it is an old browser version. + if (state == 0) { + // explicit `"state": 0` means disabled state + continue; + } + } else if (const auto* disable_reasons = dict.FindList("disable_reasons"); + disable_reasons && !disable_reasons->empty()) { + // For new browsers an extension is enabled if there is no disable reason. continue; + } - if (!dict.FindBool("from_webstore").value_or(false)) + if (!dict.FindBool("from_webstore").value_or(false)) { continue; + } if (auto* manifest_dict = dict.FindDict("manifest")) { if (Manifest::GetTypeFromManifestValue(*manifest_dict) == diff --git a/test/BUILD.gn b/test/BUILD.gn index bbed627be2b..89dcb95fea1 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -1212,6 +1212,7 @@ test("brave_browser_tests") { ] deps += [ "//brave/browser/extensions/api/identity/test:browsertests", + "//brave/browser/importer:browser_tests", "//brave/components/brave_webtorrent/browser", ] }