| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <ostream> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/check_is_test.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/functional/concurrent_closures.h" |
| #include "base/location.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/run_loop.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/commands/web_app_command.h" |
| #include "chrome/browser/web_applications/extensions_manager.h" |
| #include "chrome/browser/web_applications/externally_managed_app_manager.h" |
| #include "chrome/browser/web_applications/file_utils_wrapper.h" |
| #include "chrome/browser/web_applications/generated_icon_fix_manager.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_installation_manager.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.h" |
| #include "chrome/browser/web_applications/manifest_update_manager.h" |
| #include "chrome/browser/web_applications/navigation_capturing_log.h" |
| #include "chrome/browser/web_applications/os_integration/os_integration_manager.h" |
| #include "chrome/browser/web_applications/os_integration/web_app_file_handler_manager.h" |
| #include "chrome/browser/web_applications/os_integration/web_app_protocol_handler_manager.h" |
| #include "chrome/browser/web_applications/policy/web_app_policy_manager.h" |
| #include "chrome/browser/web_applications/preinstalled_web_app_manager.h" |
| #include "chrome/browser/web_applications/visited_manifest_manager.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/browser/web_applications/web_app_audio_focus_id_map.h" |
| #include "chrome/browser/web_applications/web_app_command_manager.h" |
| #include "chrome/browser/web_applications/web_app_command_scheduler.h" |
| #include "chrome/browser/web_applications/web_app_database_factory.h" |
| #include "chrome/browser/web_applications/web_app_icon_manager.h" |
| #include "chrome/browser/web_applications/web_app_install_finalizer.h" |
| #include "chrome/browser/web_applications/web_app_install_manager.h" |
| #include "chrome/browser/web_applications/web_app_origin_association_manager.h" |
| #include "chrome/browser/web_applications/web_app_profile_deletion_manager.h" |
| #include "chrome/browser/web_applications/web_app_provider_factory.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "chrome/browser/web_applications/web_app_sync_bridge.h" |
| #include "chrome/browser/web_applications/web_app_translation_manager.h" |
| #include "chrome/browser/web_applications/web_app_ui_manager.h" |
| #include "chrome/browser/web_applications/web_app_utils.h" |
| #include "chrome/browser/web_applications/web_contents/web_contents_manager.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "content/public/browser/web_contents.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "ash/constants/ash_features.h" |
| #include "chrome/browser/web_applications/ash/migrations/adobe_express_oem_to_default_migration.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_manager.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.h" |
| #include "chrome/browser/web_applications/web_app_run_on_os_login_manager.h" |
| #include "chromeos/ash/components/browser_context_helper/browser_context_types.h" |
| #endif |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "base/feature_list.h" |
| #include "base/mac/mac_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/rand_util.h" |
| #include "chrome/browser/web_applications/commands/rewrite_diy_icons_command.h" |
| #include "chrome/browser/web_applications/os_integration/mac/apps_folder_support.h" |
| #include "chrome/browser/web_applications/os_integration/mac/web_app_shortcut_creator.h" |
| #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h" |
| #include "chrome/browser/web_applications/web_app_registry_update.h" |
| #include "content/public/browser/browser_thread.h" |
| #endif |
| namespace webapps { |
| enum class WebappInstallSource; |
| } |
| |
| namespace web_app { |
| |
| #if BUILDFLAG(IS_MAC) |
| BASE_FEATURE(kDiyAppIconsMaskedOnMacUpdate, |
| "DiyAppIconsMaskedOnMacUpdate", |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| #endif |
| |
| // static |
| WebAppProvider* WebAppProvider::GetDeprecated(Profile* profile) { |
| return WebAppProviderFactory::GetForProfile(profile); |
| } |
| |
| // static |
| WebAppProvider* WebAppProvider::GetForWebApps(Profile* profile) { |
| return WebAppProviderFactory::GetForProfile(profile); |
| } |
| |
| // static |
| WebAppProvider* WebAppProvider::GetForLocalAppsUnchecked(Profile* profile) { |
| return WebAppProviderFactory::GetForProfile(profile); |
| } |
| |
| // static |
| WebAppProvider* WebAppProvider::GetForTest(Profile* profile) { |
| // Running a nested base::RunLoop outside of tests causes a deadlock. Crash |
| // immediately instead of deadlocking for easier debugging (especially for |
| // TAST tests which use prod binaries). |
| CHECK_IS_TEST(); |
| |
| WebAppProvider* provider = GetForLocalAppsUnchecked(profile); |
| if (!provider) { |
| return nullptr; |
| } |
| |
| if (provider->on_registry_ready().is_signaled()) { |
| return provider; |
| } |
| |
| base::RunLoop run_loop; |
| provider->on_registry_ready().Post(FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| return provider; |
| } |
| |
| // static |
| WebAppProvider* WebAppProvider::GetForWebContents( |
| content::WebContents* web_contents) { |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| DCHECK(profile); |
| return WebAppProvider::GetForLocalAppsUnchecked(profile); |
| } |
| |
| WebAppProvider::WebAppProvider(Profile* profile) : profile_(profile) { |
| DCHECK(AreWebAppsEnabled(profile_)); |
| |
| // WebApp System must have only one instance in original profile. |
| // Exclude secondary off-the-record profiles. |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (!profile_->IsGuestSession()) { |
| DCHECK(!profile_->IsOffTheRecord()); |
| } |
| #else |
| DCHECK(!profile_->IsOffTheRecord()); |
| #endif |
| |
| CreateSubsystems(profile_); |
| } |
| |
| WebAppProvider::~WebAppProvider() = default; |
| |
| void WebAppProvider::Start() { |
| CHECK(!started_); |
| |
| ConnectSubsystems(); |
| started_ = true; |
| |
| StartImpl(); |
| } |
| |
| WebAppCommandScheduler& WebAppProvider::scheduler() { |
| return *command_scheduler_; |
| } |
| |
| WebAppCommandManager& WebAppProvider::command_manager() { |
| // Note: It is OK to access the command manager before connection or start. |
| // Internally it will queue commands to only happen after it has started. |
| return *command_manager_; |
| } |
| |
| WebAppRegistrar& WebAppProvider::registrar_unsafe() { |
| CheckIsConnected(); |
| return *registrar_; |
| } |
| |
| const WebAppRegistrar& WebAppProvider::registrar_unsafe() const { |
| CheckIsConnected(); |
| return *registrar_; |
| } |
| |
| WebAppRegistrarMutable& WebAppProvider::registrar_mutable( |
| base::PassKey<WebAppSyncBridge>) { |
| CheckIsConnected(); |
| return *registrar_; |
| } |
| |
| WebAppSyncBridge& WebAppProvider::sync_bridge_unsafe() { |
| CheckIsConnected(); |
| return *sync_bridge_; |
| } |
| |
| WebAppInstallManager& WebAppProvider::install_manager() { |
| CheckIsConnected(); |
| return *install_manager_; |
| } |
| |
| WebAppInstallFinalizer& WebAppProvider::install_finalizer() { |
| CheckIsConnected(); |
| return *install_finalizer_; |
| } |
| |
| ManifestUpdateManager& WebAppProvider::manifest_update_manager() { |
| CheckIsConnected(); |
| return *manifest_update_manager_; |
| } |
| |
| ExternallyManagedAppManager& WebAppProvider::externally_managed_app_manager() { |
| CheckIsConnected(); |
| return *externally_managed_app_manager_; |
| } |
| |
| WebAppPolicyManager& WebAppProvider::policy_manager() { |
| CheckIsConnected(); |
| return *web_app_policy_manager_; |
| } |
| |
| IsolatedWebAppInstallationManager& |
| WebAppProvider::isolated_web_app_installation_manager() { |
| CheckIsConnected(); |
| return *isolated_web_app_installation_manager_; |
| } |
| |
| IsolatedWebAppUpdateManager& WebAppProvider::iwa_update_manager() { |
| CheckIsConnected(); |
| return *iwa_update_manager_; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| WebAppRunOnOsLoginManager& WebAppProvider::run_on_os_login_manager() { |
| CheckIsConnected(); |
| return *web_app_run_on_os_login_manager_; |
| } |
| |
| IwaBundleCacheManager& WebAppProvider::iwa_cache_manager() { |
| CheckIsConnected(); |
| return *iwa_cache_manager_; |
| } |
| #endif |
| |
| IsolatedWebAppPolicyManager& WebAppProvider::iwa_policy_manager() { |
| CheckIsConnected(); |
| return *isolated_web_app_policy_manager_; |
| } |
| |
| WebAppUiManager& WebAppProvider::ui_manager() { |
| CheckIsConnected(); |
| return *ui_manager_; |
| } |
| |
| WebAppAudioFocusIdMap& WebAppProvider::audio_focus_id_map() { |
| CheckIsConnected(); |
| return *audio_focus_id_map_; |
| } |
| |
| scoped_refptr<FileUtilsWrapper> WebAppProvider::file_utils() { |
| CheckIsConnected(); |
| return file_utils_; |
| } |
| |
| WebAppIconManager& WebAppProvider::icon_manager() { |
| CheckIsConnected(); |
| return *icon_manager_; |
| } |
| |
| WebAppTranslationManager& WebAppProvider::translation_manager() { |
| CheckIsConnected(); |
| return *translation_manager_; |
| } |
| |
| OsIntegrationManager& WebAppProvider::os_integration_manager() { |
| CheckIsConnected(); |
| return *os_integration_manager_; |
| } |
| |
| const OsIntegrationManager& WebAppProvider::os_integration_manager() const { |
| CheckIsConnected(); |
| return *os_integration_manager_; |
| } |
| |
| WebAppOriginAssociationManager& WebAppProvider::origin_association_manager() { |
| return *origin_association_manager_; |
| } |
| |
| WebContentsManager& WebAppProvider::web_contents_manager() { |
| return *web_contents_manager_; |
| } |
| |
| PreinstalledWebAppManager& WebAppProvider::preinstalled_web_app_manager() { |
| return *preinstalled_web_app_manager_; |
| } |
| |
| ExtensionsManager& WebAppProvider::extensions_manager() { |
| return *extensions_manager_; |
| } |
| |
| GeneratedIconFixManager& WebAppProvider::generated_icon_fix_manager() { |
| return *generated_icon_fix_manager_; |
| } |
| |
| AbstractWebAppDatabaseFactory& WebAppProvider::database_factory() { |
| return *database_factory_; |
| } |
| |
| VisitedManifestManager& WebAppProvider::visited_manifest_manager() { |
| CheckIsConnected(); |
| return *visited_manifest_manager_; |
| } |
| |
| NavigationCapturingLog& WebAppProvider::navigation_capturing_log() { |
| CheckIsConnected(); |
| return *navigation_capturing_log_; |
| } |
| |
| void WebAppProvider::Shutdown() { |
| command_scheduler_->Shutdown(); |
| // The `command_manager_` has already shut down at this point if the profile |
| // was managed by a ProfileManager that was being destroyed, but this still |
| // happens here because: |
| // 1. One shutdown is enough, duplicate shut downs do not affect the working |
| // of the `command_manager_`. |
| // 2. Sometimes a profile is used without a `ProfileManager` (like in some |
| // tests). In those cases, the `command_manager_` needs to be explicitly |
| // shutdown. |
| command_manager_->Shutdown(); |
| ui_manager_->Shutdown(); |
| externally_managed_app_manager_->Shutdown(); |
| manifest_update_manager_->Shutdown(); |
| iwa_update_manager_->Shutdown(); |
| install_manager_->Shutdown(); |
| web_app_policy_manager_->Shutdown(); |
| icon_manager_->Shutdown(); |
| install_finalizer_->Shutdown(); |
| profile_deletion_manager_->Shutdown(); |
| is_registry_ready_ = false; |
| } |
| |
| base::WeakPtr<WebAppProvider> WebAppProvider::AsWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| FakeWebAppProvider* WebAppProvider::AsFakeWebAppProviderForTesting() { |
| return nullptr; |
| } |
| |
| void WebAppProvider::StartImpl() { |
| StartSyncBridge(); |
| } |
| |
| void WebAppProvider::CreateSubsystems(Profile* profile) { |
| audio_focus_id_map_ = std::make_unique<WebAppAudioFocusIdMap>(); |
| ui_manager_ = WebAppUiManager::Create(profile); |
| install_manager_ = std::make_unique<WebAppInstallManager>(profile); |
| manifest_update_manager_ = std::make_unique<ManifestUpdateManager>(); |
| externally_managed_app_manager_ = |
| std::make_unique<ExternallyManagedAppManager>(profile); |
| preinstalled_web_app_manager_ = |
| std::make_unique<PreinstalledWebAppManager>(profile); |
| web_app_policy_manager_ = std::make_unique<WebAppPolicyManager>(profile); |
| isolated_web_app_installation_manager_ = |
| std::make_unique<IsolatedWebAppInstallationManager>(*profile); |
| iwa_update_manager_ = std::make_unique<IsolatedWebAppUpdateManager>(*profile); |
| isolated_web_app_policy_manager_ = |
| std::make_unique<IsolatedWebAppPolicyManager>(profile); |
| extensions_manager_ = std::make_unique<ExtensionsManager>(profile); |
| generated_icon_fix_manager_ = std::make_unique<GeneratedIconFixManager>(); |
| |
| database_factory_ = std::make_unique<WebAppDatabaseFactory>(profile); |
| |
| registrar_ = std::make_unique<WebAppRegistrarMutable>(profile); |
| sync_bridge_ = std::make_unique<WebAppSyncBridge>(registrar_.get()); |
| |
| file_utils_ = base::MakeRefCounted<FileUtilsWrapper>(); |
| |
| icon_manager_ = std::make_unique<WebAppIconManager>(profile); |
| translation_manager_ = std::make_unique<WebAppTranslationManager>(profile); |
| install_finalizer_ = std::make_unique<WebAppInstallFinalizer>(profile); |
| |
| auto file_handler_manager = |
| std::make_unique<WebAppFileHandlerManager>(profile); |
| auto protocol_handler_manager = |
| std::make_unique<WebAppProtocolHandlerManager>(profile); |
| |
| os_integration_manager_ = std::make_unique<OsIntegrationManager>( |
| profile, std::move(file_handler_manager), |
| std::move(protocol_handler_manager)); |
| |
| command_manager_ = std::make_unique<WebAppCommandManager>(profile); |
| command_scheduler_ = std::make_unique<WebAppCommandScheduler>(*profile); |
| |
| origin_association_manager_ = |
| std::make_unique<WebAppOriginAssociationManager>(); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| web_app_run_on_os_login_manager_ = |
| std::make_unique<WebAppRunOnOsLoginManager>(profile); |
| iwa_cache_manager_ = std::make_unique<IwaBundleCacheManager>(*profile); |
| #endif |
| |
| web_contents_manager_ = std::make_unique<WebContentsManager>(); |
| visited_manifest_manager_ = std::make_unique<VisitedManifestManager>(); |
| navigation_capturing_log_ = std::make_unique<NavigationCapturingLog>(); |
| profile_deletion_manager_ = |
| std::make_unique<WebAppProfileDeletionManager>(profile); |
| } |
| |
| void WebAppProvider::ConnectSubsystems() { |
| DCHECK(!started_); |
| |
| // TODO(https://issuetracker.google.com/283816014): Replace SetSubsystems() |
| // with SetProvider(). |
| sync_bridge_->SetSubsystems(database_factory_.get(), command_manager_.get(), |
| command_scheduler_.get(), install_manager_.get()); |
| |
| base::PassKey<WebAppProvider> pass_key; |
| icon_manager_->SetProvider(pass_key, *this); |
| install_finalizer_->SetProvider(pass_key, *this); |
| manifest_update_manager_->SetProvider(pass_key, *this); |
| externally_managed_app_manager_->SetProvider(pass_key, *this); |
| preinstalled_web_app_manager_->SetProvider(pass_key, *this); |
| web_app_policy_manager_->SetProvider(pass_key, *this); |
| registrar_->SetProvider(pass_key, *this); |
| os_integration_manager_->SetProvider(pass_key, *this); |
| command_manager_->SetProvider(pass_key, *this); |
| command_scheduler_->SetProvider(pass_key, *this); |
| isolated_web_app_installation_manager_->SetProvider(pass_key, *this); |
| iwa_update_manager_->SetProvider(pass_key, *this); |
| isolated_web_app_policy_manager_->SetProvider(pass_key, *this); |
| #if BUILDFLAG(IS_CHROMEOS) |
| web_app_run_on_os_login_manager_->SetProvider(pass_key, *this); |
| iwa_cache_manager_->SetProvider(pass_key, *this); |
| #endif |
| icon_manager_->SetProvider(pass_key, *this); |
| translation_manager_->SetProvider(pass_key, *this); |
| generated_icon_fix_manager_->SetProvider(pass_key, *this); |
| profile_deletion_manager_->SetProvider(pass_key, *this); |
| |
| connected_ = true; |
| } |
| |
| void WebAppProvider::StartSyncBridge() { |
| sync_bridge_->Init( |
| base::BindOnce(&WebAppProvider::OnSyncBridgeReady, AsWeakPtr())); |
| } |
| |
| void WebAppProvider::OnSyncBridgeReady() { |
| DCHECK(!on_registry_ready_.is_signaled()); |
| |
| // Perform database migrations once the sync bridge is ready, but before |
| // starting the rest of the subsystems and notifying that the registry is |
| // ready. |
| #if BUILDFLAG(IS_CHROMEOS) |
| web_app::migrations::MigrateAdobeExpressFromOemInstallToDefault( |
| sync_bridge_.get()); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| base::ConcurrentClosures concurrent; |
| |
| base::OnceClosure on_web_app_policy_manager_done_callback = |
| #if BUILDFLAG(IS_CHROMEOS) |
| base::BindOnce(&WebAppRunOnOsLoginManager::Start, |
| web_app_run_on_os_login_manager_->GetWeakPtr()) |
| .Then(concurrent.CreateClosure()); |
| #else |
| concurrent.CreateClosure(); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| registrar_->Start(); |
| install_finalizer_->Start(); |
| icon_manager_->Start(); |
| translation_manager_->Start(); |
| install_manager_->Start(); |
| preinstalled_web_app_manager_->Start(concurrent.CreateClosure()); |
| web_app_policy_manager_->Start( |
| std::move(on_web_app_policy_manager_done_callback)); |
| isolated_web_app_installation_manager_->Start(); |
| iwa_update_manager_->Start(); |
| isolated_web_app_policy_manager_->Start(concurrent.CreateClosure()); |
| manifest_update_manager_->Start(); |
| os_integration_manager_->Start(); |
| ui_manager_->Start(); |
| generated_icon_fix_manager_->Start(); |
| command_manager_->Start(); |
| profile_deletion_manager_->Start(); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| iwa_cache_manager_->Start(); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| // Note: This does not wait for the call from the ChromeOS |
| // SystemWebAppManager, which is a separate keyed service. |
| std::move(concurrent) |
| .Done(base::BindOnce( |
| [](base::WeakPtr<WebAppProvider> provider) { |
| if (!provider) { |
| return; |
| } |
| provider->on_external_managers_synchronized_.Signal(); |
| }, |
| AsWeakPtr())); |
| |
| on_registry_ready_.Signal(); |
| is_registry_ready_ = true; |
| |
| #if BUILDFLAG(IS_MAC) |
| if (base::FeatureList::IsEnabled(kDiyAppIconsMaskedOnMacUpdate)) { |
| content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT}) |
| ->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&WebAppProvider::DoDelayedPostStartupWork, |
| AsWeakPtr()), |
| base::Minutes(1)); |
| } |
| #endif |
| } |
| |
| void WebAppProvider::CheckIsConnected() const { |
| DCHECK(connected_) << "Attempted to access Web App subsystem while " |
| "WebAppProvider is not connected. You may need to wait " |
| "for on_registry_ready()."; |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| void WebAppProvider::DoDelayedPostStartupWork() { |
| CHECK(base::FeatureList::IsEnabled(kDiyAppIconsMaskedOnMacUpdate)); |
| |
| const WebAppRegistrar& registrar = registrar_unsafe(); |
| |
| for (const auto& app : registrar.GetApps()) { |
| // Skip apps that don't match our criteria |
| if (!registrar.AppMatches(app.app_id(), |
| WebAppFilter::IsDiyWithOsShortcut())) { |
| continue; |
| } |
| |
| // Skip apps that are already masked |
| if (registrar.IsDiyAppIconsMarkedMaskedOnMac(app.app_id())) { |
| continue; |
| } |
| |
| // Skip apps with open windows |
| if (ui_manager_->GetNumWindowsForApp(app.app_id()) != 0) { |
| continue; |
| } |
| |
| // Schedule the command for eligible apps |
| scheduler().RewriteDiyIcons(app.app_id(), base::DoNothing()); |
| } |
| } |
| #endif |
| |
| } // namespace web_app |