From 45904ffc9c5b5397f7707d2afaf6c0db6ace4282 Mon Sep 17 00:00:00 2001 From: Cepera Date: Tue, 1 Nov 2022 13:52:26 +0300 Subject: [PATCH] Added parallel import (#15674) * Added opportunity to make multiple import requests in parallel --- .../brave_external_process_importer_host.cc | 9 + .../brave_external_process_importer_host.h | 5 + browser/ui/BUILD.gn | 12 +- browser/ui/webui/settings/BUILD.gn | 21 ++ ...full_disk_access_confirm_dialog_delegate.h | 44 ++++ ...disk_access_confirm_dialog_delegate_mac.mm | 66 ++++++ .../settings/brave_import_data_handler.cc | 172 +++++++++++++++ .../settings/brave_import_data_handler.h | 21 +- .../settings/brave_import_data_handler_mac.mm | 198 ------------------ .../webui/settings/brave_importer_observer.cc | 83 ++++++++ .../webui/settings/brave_importer_observer.h | 48 +++++ .../brave_importer_observer_unittest.cc | 141 +++++++++++++ browser/ui/webui/settings/import_feature.cc | 22 ++ browser/ui/webui/settings/import_feature.h | 15 ++ test/BUILD.gn | 1 + 15 files changed, 652 insertions(+), 206 deletions(-) create mode 100644 browser/ui/webui/settings/BUILD.gn create mode 100644 browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate.h create mode 100644 browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate_mac.mm delete mode 100644 browser/ui/webui/settings/brave_import_data_handler_mac.mm create mode 100644 browser/ui/webui/settings/brave_importer_observer.cc create mode 100644 browser/ui/webui/settings/brave_importer_observer.h create mode 100644 browser/ui/webui/settings/brave_importer_observer_unittest.cc create mode 100644 browser/ui/webui/settings/import_feature.cc create mode 100644 browser/ui/webui/settings/import_feature.h diff --git a/browser/importer/brave_external_process_importer_host.cc b/browser/importer/brave_external_process_importer_host.cc index 82a0fcc0c25a..c059fe3b2a10 100644 --- a/browser/importer/brave_external_process_importer_host.cc +++ b/browser/importer/brave_external_process_importer_host.cc @@ -199,4 +199,13 @@ void BraveExternalProcessImporterHost::SetInstallExtensionCallbackForTesting( install_extension_callback_for_testing_ = std::move(callback); } +void BraveExternalProcessImporterHost::NotifyImportEndedForTesting() { + ExternalProcessImporterHost::NotifyImportEnded(); +} + +importer::ImporterProgressObserver* +BraveExternalProcessImporterHost::GetObserverForTesting() { + return observer_; +} + #endif diff --git a/browser/importer/brave_external_process_importer_host.h b/browser/importer/brave_external_process_importer_host.h index 701e32f028d8..5d0f2f3b8db8 100644 --- a/browser/importer/brave_external_process_importer_host.h +++ b/browser/importer/brave_external_process_importer_host.h @@ -30,6 +30,9 @@ class BraveExternalProcessImporterHost : public ExternalProcessImporterHost { friend class ExternalProcessImporterHost; friend class BraveExternalProcessImporterHostUnitTest; + FRIEND_TEST_ALL_PREFIXES(BraveImporterObserverUnitTest, ImportEvents); + FRIEND_TEST_ALL_PREFIXES(BraveImporterObserverUnitTest, DestroyObserverEarly); + ~BraveExternalProcessImporterHost() override; void OnExtensionInstalled(const std::string& extension_id, bool success, @@ -42,6 +45,8 @@ class BraveExternalProcessImporterHost : public ExternalProcessImporterHost { void DoNotLaunchImportForTesting(); void SetInstallExtensionCallbackForTesting(MockedInstallCallback callback); void SetSettingsRemovedCallbackForTesting(base::RepeatingClosure callback); + void NotifyImportEndedForTesting(); + importer::ImporterProgressObserver* GetObserverForTesting(); // ExternalProcessImporterHost overrides: void NotifyImportEnded() override; diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index a6898ff3eaec..e7d8062e7557 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -139,7 +139,10 @@ source_set("ui") { "webui/settings/brave_adblock_handler.h", "webui/settings/brave_appearance_handler.cc", "webui/settings/brave_appearance_handler.h", + "webui/settings/brave_import_data_handler.cc", "webui/settings/brave_import_data_handler.h", + "webui/settings/brave_importer_observer.cc", + "webui/settings/brave_importer_observer.h", "webui/settings/brave_privacy_handler.cc", "webui/settings/brave_privacy_handler.h", "webui/settings/brave_search_engines_handler.cc", @@ -152,6 +155,8 @@ source_set("ui") { "webui/settings/brave_wallet_handler.h", "webui/settings/default_brave_shields_handler.cc", "webui/settings/default_brave_shields_handler.h", + "webui/settings/import_feature.cc", + "webui/settings/import_feature.h", "webui/speedreader/speedreader_panel_data_handler_impl.cc", "webui/speedreader/speedreader_panel_data_handler_impl.h", "webui/speedreader/speedreader_panel_handler_impl.cc", @@ -168,9 +173,10 @@ source_set("ui") { } if (is_mac) { - sources += [ "webui/settings/brave_import_data_handler_mac.mm" ] - } else { - sources += [ "webui/settings/brave_import_data_handler.cc" ] + sources += [ + "webui/settings/brave_full_disk_access_confirm_dialog_delegate.h", + "webui/settings/brave_full_disk_access_confirm_dialog_delegate_mac.mm", + ] } if (enable_sparkle) { diff --git a/browser/ui/webui/settings/BUILD.gn b/browser/ui/webui/settings/BUILD.gn new file mode 100644 index 000000000000..7460df8b6748 --- /dev/null +++ b/browser/ui/webui/settings/BUILD.gn @@ -0,0 +1,21 @@ +# Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. + +source_set("unittests") { + if (!is_android) { + testonly = true + + sources = [ "brave_importer_observer_unittest.cc" ] + + deps = [ + "//base/test:test_support", + "//brave/browser/ui", + "//chrome/browser", + "//chrome/browser/ui", + "//content/test:test_support", + "//testing/gtest", + ] + } +} diff --git a/browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate.h b/browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate.h new file mode 100644 index 000000000000..dd57057c7dff --- /dev/null +++ b/browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_WEBUI_SETTINGS_BRAVE_FULL_DISK_ACCESS_CONFIRM_DIALOG_DELEGATE_H_ +#define BRAVE_BROWSER_UI_WEBUI_SETTINGS_BRAVE_FULL_DISK_ACCESS_CONFIRM_DIALOG_DELEGATE_H_ + +#include + +#include "base/memory/raw_ptr.h" +#include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h" + +namespace content { +class WebContents; +} // namespace content + +class Browser; + +class FullDiskAccessConfirmDialogDelegate + : public TabModalConfirmDialogDelegate { + public: + FullDiskAccessConfirmDialogDelegate(content::WebContents* web_contents, + Browser* browser); + ~FullDiskAccessConfirmDialogDelegate() override; + + FullDiskAccessConfirmDialogDelegate( + const FullDiskAccessConfirmDialogDelegate&) = delete; + FullDiskAccessConfirmDialogDelegate& operator=( + const FullDiskAccessConfirmDialogDelegate&) = delete; + + private: + // TabModalConfirmDialogDelegate overrides: + std::u16string GetTitle() override; + std::u16string GetDialogMessage() override; + std::u16string GetLinkText() const override; + std::u16string GetAcceptButtonTitle() override; + void OnAccepted() override; + void OnLinkClicked(WindowOpenDisposition disposition) override; + + raw_ptr browser_; +}; + +#endif // BRAVE_BROWSER_UI_WEBUI_SETTINGS_BRAVE_FULL_DISK_ACCESS_CONFIRM_DIALOG_DELEGATE_H_ diff --git a/browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate_mac.mm b/browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate_mac.mm new file mode 100644 index 000000000000..68a5b4c81b57 --- /dev/null +++ b/browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate_mac.mm @@ -0,0 +1,66 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate.h" + +#import + +#include "base/bind.h" +#include "base/values.h" +#include "brave/components/constants/url_constants.h" +#include "brave/components/l10n/common/localization_util.h" +#include "chrome/browser/importer/external_process_importer_host.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/tab_modal_confirm_dialog.h" +#include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h" +#include "chrome/common/importer/importer_data_types.h" +#include "chrome/grit/generated_resources.h" +#include "content/public/browser/web_ui.h" +#include "ui/base/ui_base_types.h" +#include "url/gurl.h" + +FullDiskAccessConfirmDialogDelegate::FullDiskAccessConfirmDialogDelegate( + content::WebContents* web_contents, + Browser* browser) + : TabModalConfirmDialogDelegate(web_contents), browser_(browser) {} + +FullDiskAccessConfirmDialogDelegate::~FullDiskAccessConfirmDialogDelegate() = + default; + +std::u16string FullDiskAccessConfirmDialogDelegate::GetTitle() { + return brave_l10n::GetLocalizedResourceUTF16String( + IDS_FULL_DISK_ACCESS_CONFIRM_DIALOG_TITLE); +} + +std::u16string FullDiskAccessConfirmDialogDelegate::GetDialogMessage() { + return brave_l10n::GetLocalizedResourceUTF16String( + IDS_FULL_DISK_ACCESS_CONFIRM_DIALOG_MESSAGE); +} + +std::u16string FullDiskAccessConfirmDialogDelegate::GetLinkText() const { + return brave_l10n::GetLocalizedResourceUTF16String( + IDS_FULL_DISK_ACCESS_CONFIRM_DIALOG_LINK_TEXT); +} + +std::u16string FullDiskAccessConfirmDialogDelegate::GetAcceptButtonTitle() { + return brave_l10n::GetLocalizedResourceUTF16String( + IDS_FULL_DISK_ACCESS_CONFIRM_DIALOG_OPEN_PREFS_BUTTON_TEXT); +} + +void FullDiskAccessConfirmDialogDelegate::OnAccepted() { + [[NSWorkspace sharedWorkspace] + openURL:[NSURL URLWithString: + @"x-apple.systempreferences:com.apple.preference." + @"security?Privacy_AllFiles"]]; // NOLINT +} + +void FullDiskAccessConfirmDialogDelegate::OnLinkClicked( + WindowOpenDisposition disposition) { + const int target_index = browser_->tab_strip_model()->active_index() + 1; + // Add import help tab right after current settings tab. + chrome::AddTabAt(browser_, GURL(kImportDataHelpURL), target_index, + true /* foreground */); +} diff --git a/browser/ui/webui/settings/brave_import_data_handler.cc b/browser/ui/webui/settings/brave_import_data_handler.cc index b1eed8840f80..9d322a530a29 100644 --- a/browser/ui/webui/settings/brave_import_data_handler.cc +++ b/browser/ui/webui/settings/brave_import_data_handler.cc @@ -5,9 +5,181 @@ #include "brave/browser/ui/webui/settings/brave_import_data_handler.h" +#include +#include +#include + +#include "brave/browser/ui/webui/settings/import_feature.h" +#include "chrome/browser/importer/external_process_importer_host.h" +#include "chrome/browser/importer/profile_writer.h" +#include "chrome/browser/profiles/profile.h" +#include "content/public/browser/browser_task_traits.h" + +#if BUILDFLAG(IS_MAC) +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/mac/foundation_util.h" +#include "base/task/thread_pool.h" +#include "brave/browser/ui/webui/settings/brave_full_disk_access_confirm_dialog_delegate.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/tab_modal_confirm_dialog.h" +#include "content/public/browser/browser_thread.h" +#endif // BUILDFLAG(IS_MAC) + +namespace { +#if BUILDFLAG(IS_MAC) +bool HasProperDiskAccessPermission(uint16_t imported_items) { + DCHECK(imported_items); + + const base::FilePath& library_dir = base::mac::GetUserLibraryPath(); + const base::FilePath safari_dir = library_dir.Append("Safari"); + + if (imported_items & importer::FAVORITES) { + const base::FilePath bookmarks_path = safari_dir.Append("Bookmarks.plist"); + if (!PathIsWritable(bookmarks_path)) { + LOG(ERROR) << __func__ << " " << bookmarks_path << " is not accessible." + << " Please check full disk access permission."; + return false; + } + } + + if (imported_items & importer::HISTORY) { + const base::FilePath history_path = safari_dir.Append("History.plist"); + if (!PathIsWritable(history_path)) { + LOG(ERROR) << __func__ << " " << history_path << " is not accessible." + << " Please check full disk access permission."; + return false; + } + } + + return true; +} +#endif // BUILDFLAG(IS_MAC) +} // namespace + namespace settings { BraveImportDataHandler::BraveImportDataHandler() = default; BraveImportDataHandler::~BraveImportDataHandler() = default; +void BraveImportDataHandler::StartImport( + const importer::SourceProfile& source_profile, + uint16_t imported_items) { + if (!imported_items) + return; + +#if BUILDFLAG(IS_MAC) + CheckDiskAccess(source_profile, imported_items); +#else + StartImportImpl(source_profile, imported_items); +#endif +} + +void BraveImportDataHandler::StartImportImpl( + const importer::SourceProfile& source_profile, + uint16_t imported_items) { + // Temporary flag to keep old way until + // https://github.com/brave/brave-core/pull/15637 landed. + // Should be removed in that PR. + if (!IsParallelImportEnabled()) { + ImportDataHandler::StartImport(source_profile, imported_items); + return; + } + // If another import is already ongoing, let it finish silently. + if (import_observers_.count(source_profile.source_path)) + import_observers_.erase(source_profile.source_path); + + // Using weak pointers because it destroys itself when finshed. + auto* importer_host = new ExternalProcessImporterHost(); + import_observers_[source_profile.source_path] = + std::make_unique( + importer_host, source_profile, imported_items, + base::BindRepeating(&BraveImportDataHandler::NotifyImportProgress, + weak_factory_.GetWeakPtr())); + Profile* profile = Profile::FromWebUI(web_ui()); + importer_host->StartImportSettings(source_profile, profile, imported_items, + new ProfileWriter(profile)); +} + +void BraveImportDataHandler::NotifyImportProgress( + const importer::SourceProfile& source_profile, + const base::Value& info) { + FireWebUIListener("brave-import-data-status-changed", info); + const std::string* event = info.FindStringKey("event"); + if (event && *event == "ImportEnded") { + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&BraveImportDataHandler::OnImportEnded, + weak_factory_.GetWeakPtr(), source_profile.source_path)); + } +} + +void BraveImportDataHandler::OnImportEnded(const base::FilePath& source_path) { + import_observers_.erase(source_path); +} + +#if BUILDFLAG(IS_MAC) +void BraveImportDataHandler::CheckDiskAccess( + const importer::SourceProfile& source_profile, + uint16_t imported_items) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + guide_dialog_is_requested_ = false; + + if (!imported_items) + return; + + if (source_profile.importer_type == importer::TYPE_SAFARI) { + // Start import if Brave has full disk access permission. + // If not, show dialog that has infos about that permission. + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::MayBlock()}, + base::BindOnce(&HasProperDiskAccessPermission, imported_items), + base::BindOnce(&BraveImportDataHandler::OnGetDiskAccessPermission, + weak_factory_.GetWeakPtr(), source_profile, + imported_items)); + return; + } + + StartImportImpl(source_profile, imported_items); +} + +void BraveImportDataHandler::OnGetDiskAccessPermission( + const importer::SourceProfile& source_profile, + uint16_t imported_items, + bool allowed) { + if (!allowed) { + // Notify to webui to finish import process and launch tab modal dialog + // to guide full disk access information to users. + // Guide dialog will be opened after import dialog is closed. + FireWebUIListener("import-data-status-changed", base::Value("failed")); + if (IsParallelImportEnabled()) { + import_observers_[source_profile.source_path]->ImportEnded(); + } + // Observing web_contents is started here to know the closing timing of + // import dialog. + Observe(web_ui()->GetWebContents()); + + guide_dialog_is_requested_ = true; + return; + } + + StartImportImpl(source_profile, imported_items); +} + +void BraveImportDataHandler::DidStopLoading() { + Observe(nullptr); + + if (!guide_dialog_is_requested_) + return; + + guide_dialog_is_requested_ = false; + + auto* web_contents = web_ui()->GetWebContents(); + TabModalConfirmDialog::Create( + std::make_unique( + web_contents, chrome::FindBrowserWithWebContents(web_contents)), + web_contents); +} +#endif } // namespace settings diff --git a/browser/ui/webui/settings/brave_import_data_handler.h b/browser/ui/webui/settings/brave_import_data_handler.h index 15abedc9d2b9..e1e55da44028 100644 --- a/browser/ui/webui/settings/brave_import_data_handler.h +++ b/browser/ui/webui/settings/brave_import_data_handler.h @@ -6,7 +6,11 @@ #ifndef BRAVE_BROWSER_UI_WEBUI_SETTINGS_BRAVE_IMPORT_DATA_HANDLER_H_ #define BRAVE_BROWSER_UI_WEBUI_SETTINGS_BRAVE_IMPORT_DATA_HANDLER_H_ +#include +#include + #include "base/memory/weak_ptr.h" +#include "brave/browser/ui/webui/settings/brave_importer_observer.h" #include "build/build_config.h" #include "chrome/browser/ui/webui/settings/import_data_handler.h" #include "content/public/browser/web_contents_observer.h" @@ -25,8 +29,6 @@ namespace settings { // new navigation start and tab is newly loaded for closing webui import dialog. // The reason why native tab modal dialog is used here is to avoid modifying // upstream import html/js source code. - -// NOTE: This is no-op class for other platforms except macOS. class BraveImportDataHandler : public ImportDataHandler, content::WebContentsObserver { public: @@ -37,11 +39,18 @@ class BraveImportDataHandler : public ImportDataHandler, BraveImportDataHandler& operator=(const BraveImportDataHandler&) = delete; private: -#if BUILDFLAG(IS_MAC) // ImportDataHandler overrides: void StartImport(const importer::SourceProfile& source_profile, uint16_t imported_items) override; + void StartImportImpl(const importer::SourceProfile& source_profile, + uint16_t imported_items); + void NotifyImportProgress(const importer::SourceProfile& source_profile, + const base::Value& info); + void OnImportEnded(const base::FilePath& source_path); +#if BUILDFLAG(IS_MAC) + void CheckDiskAccess(const importer::SourceProfile& source_profile, + uint16_t imported_items); void OnGetDiskAccessPermission(const importer::SourceProfile& source_profile, uint16_t imported_items, bool allowed); @@ -50,9 +59,11 @@ class BraveImportDataHandler : public ImportDataHandler, void DidStopLoading() override; bool guide_dialog_is_requested_ = false; - - base::WeakPtrFactory weak_factory_; #endif + + std::unordered_map> + import_observers_; + base::WeakPtrFactory weak_factory_{this}; }; } // namespace settings diff --git a/browser/ui/webui/settings/brave_import_data_handler_mac.mm b/browser/ui/webui/settings/brave_import_data_handler_mac.mm deleted file mode 100644 index b86c3453cf23..000000000000 --- a/browser/ui/webui/settings/brave_import_data_handler_mac.mm +++ /dev/null @@ -1,198 +0,0 @@ -/* Copyright (c) 2020 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 http://mozilla.org/MPL/2.0/. */ - -#include "brave/browser/ui/webui/settings/brave_import_data_handler.h" - -#import - -#include "base/bind.h" -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/mac/foundation_util.h" -#include "base/task/thread_pool.h" -#include "base/values.h" -#include "brave/components/constants/url_constants.h" -#include "brave/components/l10n/common/localization_util.h" -#include "chrome/browser/importer/external_process_importer_host.h" -#include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/browser_finder.h" -#include "chrome/browser/ui/browser_tabstrip.h" -#include "chrome/browser/ui/tab_modal_confirm_dialog.h" -#include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h" -#include "chrome/common/importer/importer_data_types.h" -#include "chrome/grit/generated_resources.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/browser/web_ui.h" -#include "ui/base/ui_base_types.h" -#include "url/gurl.h" - -namespace { - -using content::BrowserThread; - -class FullDiskAccessConfirmDialogDelegate - : public TabModalConfirmDialogDelegate { - public: - FullDiskAccessConfirmDialogDelegate(content::WebContents* web_contents, - Browser* browser); - ~FullDiskAccessConfirmDialogDelegate() override; - - FullDiskAccessConfirmDialogDelegate( - const FullDiskAccessConfirmDialogDelegate&) = delete; - FullDiskAccessConfirmDialogDelegate& operator=( - const FullDiskAccessConfirmDialogDelegate&) = delete; - - private: - // TabModalConfirmDialogDelegate overrides: - std::u16string GetTitle() override; - std::u16string GetDialogMessage() override; - std::u16string GetLinkText() const override; - std::u16string GetAcceptButtonTitle() override; - void OnAccepted() override; - void OnLinkClicked(WindowOpenDisposition disposition) override; - - Browser* browser_; -}; - -FullDiskAccessConfirmDialogDelegate::FullDiskAccessConfirmDialogDelegate( - content::WebContents* web_contents, - Browser* browser) - : TabModalConfirmDialogDelegate(web_contents), - browser_(browser) {} - -FullDiskAccessConfirmDialogDelegate:: - ~FullDiskAccessConfirmDialogDelegate() = default; - -std::u16string FullDiskAccessConfirmDialogDelegate::GetTitle() { - return brave_l10n::GetLocalizedResourceUTF16String( - IDS_FULL_DISK_ACCESS_CONFIRM_DIALOG_TITLE); -} - -std::u16string FullDiskAccessConfirmDialogDelegate::GetDialogMessage() { - return brave_l10n::GetLocalizedResourceUTF16String( - IDS_FULL_DISK_ACCESS_CONFIRM_DIALOG_MESSAGE); -} - -std::u16string FullDiskAccessConfirmDialogDelegate::GetLinkText() const { - return brave_l10n::GetLocalizedResourceUTF16String( - IDS_FULL_DISK_ACCESS_CONFIRM_DIALOG_LINK_TEXT); -} - -std::u16string FullDiskAccessConfirmDialogDelegate::GetAcceptButtonTitle() { - return brave_l10n::GetLocalizedResourceUTF16String( - IDS_FULL_DISK_ACCESS_CONFIRM_DIALOG_OPEN_PREFS_BUTTON_TEXT); -} - -void FullDiskAccessConfirmDialogDelegate::OnAccepted() { - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString: - @"x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"]]; // NOLINT -} - -void FullDiskAccessConfirmDialogDelegate::OnLinkClicked( - WindowOpenDisposition disposition) { - const int target_index = - browser_->tab_strip_model()->active_index() + 1; - // Add import help tab right after current settings tab. - chrome::AddTabAt(browser_, GURL(kImportDataHelpURL), - target_index, true /* foreground */); -} - -bool HasProperDiskAccessPermission(uint16_t imported_items) { - DCHECK(imported_items); - - const base::FilePath& library_dir = base::mac::GetUserLibraryPath(); - const base::FilePath safari_dir = library_dir.Append("Safari"); - - if (imported_items & importer::FAVORITES) { - const base::FilePath bookmarks_path = safari_dir.Append("Bookmarks.plist"); - if(!PathIsWritable(bookmarks_path)) { - LOG(ERROR) << __func__ << " " << bookmarks_path << " is not accessible." - << " Please check full disk access permission."; - return false; - } - } - - if (imported_items & importer::HISTORY) { - const base::FilePath history_path = safari_dir.Append("History.plist"); - if(!PathIsWritable(history_path)) { - LOG(ERROR) << __func__ << " " << history_path << " is not accessible." - << " Please check full disk access permission."; - return false; - } - } - - return true; -} - -} // namespace - -namespace settings { - -BraveImportDataHandler::BraveImportDataHandler() : weak_factory_(this) {} -BraveImportDataHandler::~BraveImportDataHandler() = default; - -void BraveImportDataHandler::StartImport( - const importer::SourceProfile& source_profile, - uint16_t imported_items) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - guide_dialog_is_requested_ = false; - - if (!imported_items) - return; - - if (source_profile.importer_type == importer::TYPE_SAFARI) { - // Start import if Brave has full disk access permission. - // If not, show dialog that has infos about that permission. - base::ThreadPool::PostTaskAndReplyWithResult( - FROM_HERE, {base::MayBlock()}, - base::BindOnce(&HasProperDiskAccessPermission, imported_items), - base::BindOnce(&BraveImportDataHandler::OnGetDiskAccessPermission, - weak_factory_.GetWeakPtr(), source_profile, - imported_items)); - return; - } - - ImportDataHandler::StartImport(source_profile, imported_items); -} - -void BraveImportDataHandler::OnGetDiskAccessPermission( - const importer::SourceProfile& source_profile, - uint16_t imported_items, - bool allowed) { - if (!allowed) { - // Notify to webui to finish import process and launch tab modal dialog - // to guide full disk access information to users. - // Guide dialog will be opened after import dialog is closed. - FireWebUIListener("import-data-status-changed", base::Value("failed")); - - // Observing web_contents is started here to know the closing timing of - // import dialog. - Observe(web_ui()->GetWebContents()); - - guide_dialog_is_requested_ = true; - return; - } - - return ImportDataHandler::StartImport(source_profile, imported_items); -} - -void BraveImportDataHandler::DidStopLoading() { - Observe(nullptr); - - if (!guide_dialog_is_requested_) - return; - - guide_dialog_is_requested_ = false; - - auto* web_contents = web_ui()->GetWebContents(); - TabModalConfirmDialog::Create( - std::make_unique( - web_contents, - chrome::FindBrowserWithWebContents(web_contents)), - web_contents); -} - -} // namespace settings diff --git a/browser/ui/webui/settings/brave_importer_observer.cc b/browser/ui/webui/settings/brave_importer_observer.cc new file mode 100644 index 000000000000..6a790f233cec --- /dev/null +++ b/browser/ui/webui/settings/brave_importer_observer.cc @@ -0,0 +1,83 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/webui/settings/brave_importer_observer.h" + +#include + +#include "base/logging.h" +#include "chrome/browser/importer/external_process_importer_host.h" + +BraveImporterObserver::BraveImporterObserver( + ExternalProcessImporterHost* importer_host, + const importer::SourceProfile& source_profile, + uint16_t imported_items, + ReportProgressCallback callback) + : source_profile_(source_profile), + imported_items_(imported_items), + callback_(std::move(callback)), + importer_host_(importer_host) { + DCHECK(importer_host); + importer_host->set_observer(this); +} + +BraveImporterObserver::~BraveImporterObserver() { + if (importer_host_) + importer_host_->set_observer(nullptr); +} + +// importer::ImporterProgressObserver: +void BraveImporterObserver::ImportStarted() { + if (import_started_called_) + return; + import_started_called_ = true; + base::Value::Dict data; + data.Set("importer_name", source_profile_.importer_name); + data.Set("importer_type", source_profile_.importer_type); + data.Set("items_to_import", imported_items_); + data.Set("event", "ImportStarted"); + callback_.Run(source_profile_, base::Value(std::move(data))); +} + +void BraveImporterObserver::ImportItemStarted(importer::ImportItem item) { + base::Value::Dict data; + data.Set("importer_name", source_profile_.importer_name); + data.Set("importer_type", source_profile_.importer_type); + data.Set("items_to_import", imported_items_); + data.Set("event", "ImportItemStarted"); + data.Set("item", item); + callback_.Run(source_profile_, base::Value(std::move(data))); +} + +void BraveImporterObserver::ImportItemEnded(importer::ImportItem item) { + base::Value::Dict data; + data.Set("importer_name", source_profile_.importer_name); + data.Set("importer_type", source_profile_.importer_type); + data.Set("items_to_import", imported_items_); + data.Set("event", "ImportItemEnded"); + data.Set("item", item); + callback_.Run(source_profile_, base::Value(std::move(data))); +} + +void BraveImporterObserver::ImportEnded() { + base::Value::Dict data; + data.Set("importer_name", source_profile_.importer_name); + data.Set("importer_type", source_profile_.importer_type); + data.Set("items_to_import", imported_items_); + data.Set("event", "ImportEnded"); + + DCHECK(importer_host_); + if (importer_host_) + importer_host_->set_observer(nullptr); + + importer_host_ = nullptr; + + callback_.Run(source_profile_, base::Value(std::move(data))); +} + +ExternalProcessImporterHost* +BraveImporterObserver::GetImporterHostForTesting() { + return importer_host_.get(); +} diff --git a/browser/ui/webui/settings/brave_importer_observer.h b/browser/ui/webui/settings/brave_importer_observer.h new file mode 100644 index 000000000000..34855d255697 --- /dev/null +++ b/browser/ui/webui/settings/brave_importer_observer.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_WEBUI_SETTINGS_BRAVE_IMPORTER_OBSERVER_H_ +#define BRAVE_BROWSER_UI_WEBUI_SETTINGS_BRAVE_IMPORTER_OBSERVER_H_ + +#include "base/callback.h" +#include "base/memory/raw_ptr.h" +#include "base/values.h" +#include "chrome/browser/importer/importer_progress_observer.h" + +class ExternalProcessImporterHost; + +class BraveImporterObserver : public importer::ImporterProgressObserver { + public: + using ReportProgressCallback = base::RepeatingCallback< + void(const importer::SourceProfile& source_profile, const base::Value&)>; + + BraveImporterObserver(ExternalProcessImporterHost* host, + const importer::SourceProfile& source_profile, + uint16_t imported_items, + ReportProgressCallback callback); + ~BraveImporterObserver() override; + + void ImportStarted() override; + void ImportItemStarted(importer::ImportItem item) override; + void ImportItemEnded(importer::ImportItem item) override; + void ImportEnded() override; + + private: + FRIEND_TEST_ALL_PREFIXES(BraveImporterObserverUnitTest, ImportEvents); + + ExternalProcessImporterHost* GetImporterHostForTesting(); + + importer::SourceProfile source_profile_; + uint16_t imported_items_ = 0; + ReportProgressCallback callback_; + // By some reasons ImportStarted event is called few times from different + // places, we expect only one call. + bool import_started_called_ = false; + // If non-null it means importing is in progress. ImporterHost takes care + // of deleting itself when import is complete. + raw_ptr importer_host_; // weak +}; + +#endif // BRAVE_BROWSER_UI_WEBUI_SETTINGS_BRAVE_IMPORTER_OBSERVER_H_ diff --git a/browser/ui/webui/settings/brave_importer_observer_unittest.cc b/browser/ui/webui/settings/brave_importer_observer_unittest.cc new file mode 100644 index 000000000000..209ede0c9ce2 --- /dev/null +++ b/browser/ui/webui/settings/brave_importer_observer_unittest.cc @@ -0,0 +1,141 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/webui/settings/brave_importer_observer.h" + +#include +#include + +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/test/bind.h" +#include "brave/browser/importer/brave_external_process_importer_host.h" +#include "content/public/test/browser_task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +class BraveImporterObserverUnitTest : public testing::Test { + public: + BraveImporterObserverUnitTest() {} + + void SetExpectedInfo(base::Value value) { expected_info_ = std::move(value); } + void SetExpectedCalls(int value) { expected_calls_ = value; } + int GetExpectedCalls() { return expected_calls_; } + void NotifyImportProgress(const importer::SourceProfile& source_profile, + const base::Value& info) { + EXPECT_EQ(expected_info_, info); + expected_calls_++; + } + + private: + content::BrowserTaskEnvironment task_environment_; + base::Value expected_info_; + int expected_calls_ = 0; + raw_ptr _ = nullptr; +}; + +TEST_F(BraveImporterObserverUnitTest, ImportEvents) { + auto* importer_host = new BraveExternalProcessImporterHost(); + importer::SourceProfile source_profile; + source_profile.importer_name = u"importer_name"; + source_profile.importer_type = importer::TYPE_CHROME; + source_profile.source_path = base::FilePath(FILE_PATH_LITERAL("test")); + auto imported_items = importer::AUTOFILL_FORM_DATA | importer::PASSWORDS; + std::unique_ptr observer = + std::make_unique( + importer_host, source_profile, imported_items, + base::BindRepeating( + &BraveImporterObserverUnitTest::NotifyImportProgress, + base::Unretained(this))); + EXPECT_EQ(importer_host->GetObserverForTesting(), observer.get()); + EXPECT_EQ(GetExpectedCalls(), 0); + + // Multiple calls for same profile. + SetExpectedInfo(*base::JSONReader::Read(R"({ + "event": "ImportStarted", + "importer_name": "importer_name", + "importer_type": 1, + "items_to_import": 72 + })")); + observer->ImportStarted(); + observer->ImportStarted(); + observer->ImportStarted(); + EXPECT_EQ(GetExpectedCalls(), 1); + + // ImportItemStarted event. + SetExpectedCalls(0); + EXPECT_EQ(GetExpectedCalls(), 0); + SetExpectedInfo(*base::JSONReader::Read(R"({ + "event": "ImportItemStarted", + "importer_name": "importer_name", + "importer_type": 1, + "item": 8, + "items_to_import": 72 + })")); + + observer->ImportItemStarted(importer::PASSWORDS); + EXPECT_EQ(GetExpectedCalls(), 1); + + // ImportItemEnded event. + SetExpectedCalls(0); + EXPECT_EQ(GetExpectedCalls(), 0); + SetExpectedInfo(*base::JSONReader::Read(R"({ + "event": "ImportItemEnded", + "importer_name": "importer_name", + "importer_type": 1, + "item": 8, + "items_to_import": 72 + })")); + + observer->ImportItemEnded(importer::PASSWORDS); + EXPECT_EQ(GetExpectedCalls(), 1); + + // ImportEnded event. + SetExpectedCalls(0); + EXPECT_EQ(GetExpectedCalls(), 0); + SetExpectedInfo(*base::JSONReader::Read(R"({ + "event": "ImportEnded", + "importer_name": "importer_name", + "importer_type": 1, + "items_to_import": 72 + })")); + + observer->ImportEnded(); + EXPECT_EQ(GetExpectedCalls(), 1); + EXPECT_EQ(observer->GetImporterHostForTesting(), nullptr); + // The observer should be removed on ImportEnded event. + EXPECT_EQ(importer_host->GetObserverForTesting(), nullptr); + + // ImportEnded event should not be called anymore. + SetExpectedCalls(0); + EXPECT_EQ(GetExpectedCalls(), 0); + // Destroy host. + importer_host->NotifyImportEndedForTesting(); + EXPECT_EQ(GetExpectedCalls(), 0); +} + +TEST_F(BraveImporterObserverUnitTest, DestroyObserverEarly) { + auto* importer_host = new BraveExternalProcessImporterHost(); + importer::SourceProfile source_profile; + source_profile.importer_name = u"importer_name"; + source_profile.importer_type = importer::TYPE_CHROME; + source_profile.source_path = base::FilePath(FILE_PATH_LITERAL("test")); + auto imported_items = importer::AUTOFILL_FORM_DATA | importer::PASSWORDS; + std::unique_ptr observer = + std::make_unique( + importer_host, source_profile, imported_items, + base::BindRepeating( + &BraveImporterObserverUnitTest::NotifyImportProgress, + base::Unretained(this))); + EXPECT_EQ(importer_host->GetObserverForTesting(), observer.get()); + EXPECT_EQ(GetExpectedCalls(), 0); + observer.reset(); + EXPECT_EQ(importer_host->GetObserverForTesting(), nullptr); + // ImportEnded event should not be called anymore. + SetExpectedCalls(0); + EXPECT_EQ(GetExpectedCalls(), 0); + // Destroy host. + importer_host->NotifyImportEndedForTesting(); + EXPECT_EQ(GetExpectedCalls(), 0); +} diff --git a/browser/ui/webui/settings/import_feature.cc b/browser/ui/webui/settings/import_feature.cc new file mode 100644 index 000000000000..ee6cda95957a --- /dev/null +++ b/browser/ui/webui/settings/import_feature.cc @@ -0,0 +1,22 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/webui/settings/import_feature.h" + +#include "base/feature_list.h" +#include "base/metrics/field_trial_params.h" + +namespace settings { + +const base::Feature kParallelImports{"ParallelImports", + base::FEATURE_ENABLED_BY_DEFAULT}; + +// Temporary flag to keep old way until +// https://github.com/brave/brave-core/pull/15637 landed. +bool IsParallelImportEnabled() { + return base::FeatureList::IsEnabled(kParallelImports); +} + +} // namespace settings diff --git a/browser/ui/webui/settings/import_feature.h b/browser/ui/webui/settings/import_feature.h new file mode 100644 index 000000000000..a4f398b5d647 --- /dev/null +++ b/browser/ui/webui/settings/import_feature.h @@ -0,0 +1,15 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_WEBUI_SETTINGS_IMPORT_FEATURE_H_ +#define BRAVE_BROWSER_UI_WEBUI_SETTINGS_IMPORT_FEATURE_H_ + +#include "base/feature_list.h" + +namespace settings { +bool IsParallelImportEnabled(); +} // namespace settings + +#endif // BRAVE_BROWSER_UI_WEBUI_SETTINGS_IMPORT_FEATURE_H_ diff --git a/test/BUILD.gn b/test/BUILD.gn index 741a96593d62..59cf91cf38c5 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -381,6 +381,7 @@ test("brave_unit_tests") { "//brave/app:brave_generated_resources_grit", "//brave/browser/ui/color:unit_tests", "//brave/browser/ui/toolbar:brave_app_menu_unit_test", + "//brave/browser/ui/webui/settings:unittests", "//brave/components/brave_shields/common:mojom", "//brave/components/brave_wallet/browser:pref_names", "//brave/components/brave_wallet/browser:utils",