| // Copyright 2021 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/lacros/lacros_extension_apps_publisher.h" |
| |
| #include <set> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/containers/extend.h" |
| #include "base/files/file_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/not_fatal_until.h" |
| #include "base/scoped_observation.h" |
| #include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h" |
| #include "chrome/browser/apps/app_service/extension_apps_utils.h" |
| #include "chrome/browser/apps/app_service/intent_util.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/extensions/web_file_handlers/intent_util.h" |
| #include "chrome/browser/extensions/extension_ui_util.h" |
| #include "chrome/browser/extensions/launch_util.h" |
| #include "chrome/browser/lacros/lacros_extensions_util.h" |
| #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" |
| #include "chrome/browser/policy/system_features_disable_list_policy_handler.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/lacros/window_utility.h" |
| #include "chrome/browser/web_applications/web_app_tab_helper.h" |
| #include "chromeos/crosapi/mojom/app_window_tracker.mojom.h" |
| #include "chromeos/lacros/lacros_service.h" |
| #include "components/app_constants/constants.h" |
| #include "components/policy/core/common/policy_pref_names.h" |
| #include "components/prefs/pref_change_registrar.h" |
| #include "components/services/app_service/public/cpp/app_capability_access_cache.h" |
| #include "components/services/app_service/public/cpp/app_types.h" |
| #include "components/services/app_service/public/cpp/icon_types.h" |
| #include "components/services/app_service/public/cpp/intent_filter.h" |
| #include "components/services/app_service/public/cpp/package_id.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/app_window/app_window.h" |
| #include "extensions/browser/app_window/app_window_registry.h" |
| #include "extensions/browser/extension_file_task_runner.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_prefs_observer.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_registry_observer.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/management_policy.h" |
| #include "extensions/browser/path_util.h" |
| #include "extensions/browser/unloaded_extension_reason.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/manifest_handlers/app_display_info.h" |
| #include "extensions/common/manifest_handlers/web_file_handlers_info.h" |
| |
| namespace { |
| |
| apps::InstallReason GetInstallReason(const extensions::Extension* extension) { |
| if (extensions::Manifest::IsComponentLocation(extension->location())) |
| return apps::InstallReason::kSystem; |
| |
| if (extensions::Manifest::IsPolicyLocation(extension->location())) |
| return apps::InstallReason::kPolicy; |
| |
| if (extension->was_installed_by_oem()) |
| return apps::InstallReason::kOem; |
| |
| if (extension->was_installed_by_default()) |
| return apps::InstallReason::kDefault; |
| |
| return apps::InstallReason::kUser; |
| } |
| |
| } // namespace |
| |
| // This class tracks all extension apps associated with a given Profile*. The |
| // observation of ExtensionPrefsObserver and ExtensionRegistryObserver is used |
| // to track AppService publisher events. The observation of AppsWindowRegistry |
| // is used to track window creation and destruction. |
| class LacrosExtensionAppsPublisher::ProfileTracker |
| : public extensions::ExtensionPrefsObserver, |
| public extensions::ExtensionRegistryObserver, |
| public extensions::AppWindowRegistry::Observer { |
| using Readiness = apps::Readiness; |
| |
| public: |
| ProfileTracker(Profile* profile, |
| LacrosExtensionAppsPublisher* publisher, |
| const ForWhichExtensionType& which_type) |
| : profile_(profile), publisher_(publisher), which_type_(which_type) { |
| // Start observing for relevant events. |
| prefs_observation_.Observe(extensions::ExtensionPrefs::Get(profile_)); |
| registry_observation_.Observe(extensions::ExtensionRegistry::Get(profile_)); |
| app_window_registry_observation_.Observe( |
| extensions::AppWindowRegistry::Get(profile_)); |
| if (auto* local_state = g_browser_process->local_state()) { |
| local_state_pref_change_registrar_.Init(local_state); |
| local_state_pref_change_registrar_.Add( |
| policy::policy_prefs::kSystemFeaturesDisableList, |
| base::BindRepeating(&LacrosExtensionAppsPublisher::ProfileTracker:: |
| OnSystemFeaturesPrefChanged, |
| weak_factory_.GetWeakPtr())); |
| local_state_pref_change_registrar_.Add( |
| policy::policy_prefs::kSystemFeaturesDisableMode, |
| base::BindRepeating(&LacrosExtensionAppsPublisher::ProfileTracker:: |
| OnSystemFeaturesPrefChanged, |
| weak_factory_.GetWeakPtr())); |
| OnSystemFeaturesPrefChanged(); |
| } |
| |
| // Populate initial conditions [e.g. installed apps prior to starting |
| // observation]. |
| std::vector<apps::AppPtr> apps; |
| extensions::ExtensionRegistry* registry = |
| extensions::ExtensionRegistry::Get(profile_); |
| for (const scoped_refptr<const extensions::Extension> extension : |
| registry->enabled_extensions()) { |
| if (which_type_.Matches(extension.get())) { |
| apps.push_back(MakeApp(extension.get(), Readiness::kReady)); |
| } |
| } |
| for (const scoped_refptr<const extensions::Extension> extension : |
| registry->disabled_extensions()) { |
| if (which_type_.Matches(extension.get())) { |
| apps.push_back(MakeApp(extension.get(), Readiness::kDisabledByUser)); |
| } |
| } |
| for (const scoped_refptr<const extensions::Extension> extension : |
| registry->terminated_extensions()) { |
| if (which_type_.Matches(extension.get())) { |
| apps.push_back(MakeApp(extension.get(), Readiness::kTerminated)); |
| } |
| } |
| if (!apps.empty()) |
| Publish(std::move(apps)); |
| |
| if (which_type_.IsChromeApps()) { |
| // Populate initial conditions [e.g. app windows created prior to starting |
| // observation]. |
| for (extensions::AppWindow* app_window : |
| extensions::AppWindowRegistry::Get(profile_)->app_windows()) { |
| OnAppWindowAdded(app_window); |
| } |
| } |
| } |
| |
| void Publish(const extensions::Extension* extension, Readiness readiness) { |
| Publish(MakeApp(extension, readiness)); |
| } |
| |
| ~ProfileTracker() override = default; |
| |
| private: |
| // extensions::ExtensionPrefsObserver overrides. |
| void OnExtensionLastLaunchTimeChanged( |
| const std::string& app_id, |
| const base::Time& last_launch_time) override { |
| const auto* extension = |
| lacros_extensions_util::MaybeGetExtension(profile_, app_id); |
| if (!extension || !which_type_.Matches(extension)) |
| return; |
| |
| Publish(MakeApp(extension, Readiness::kReady)); |
| } |
| |
| void OnExtensionPrefsWillBeDestroyed( |
| extensions::ExtensionPrefs* prefs) override { |
| DCHECK(prefs_observation_.IsObservingSource(prefs)); |
| prefs_observation_.Reset(); |
| } |
| |
| // extensions::ExtensionRegistryObserver overrides. |
| void OnExtensionLoaded(content::BrowserContext* browser_context, |
| const extensions::Extension* extension) override { |
| if (!which_type_.Matches(extension)) |
| return; |
| Publish(MakeApp(extension, Readiness::kReady)); |
| } |
| |
| void OnExtensionUnloaded( |
| content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| extensions::UnloadedExtensionReason reason) override { |
| if (!which_type_.Matches(extension)) |
| return; |
| |
| Readiness readiness = Readiness::kUnknown; |
| |
| switch (reason) { |
| case extensions::UnloadedExtensionReason::DISABLE: |
| readiness = Readiness::kDisabledByUser; |
| break; |
| case extensions::UnloadedExtensionReason::BLOCKLIST: |
| readiness = Readiness::kDisabledByBlocklist; |
| break; |
| case extensions::UnloadedExtensionReason::TERMINATE: |
| readiness = Readiness::kTerminated; |
| break; |
| case extensions::UnloadedExtensionReason::UNINSTALL: |
| // App readiness will be updated by OnExtensionUninstalled(). We defer |
| // to that method. |
| return; |
| case extensions::UnloadedExtensionReason::UNDEFINED: |
| case extensions::UnloadedExtensionReason::UPDATE: |
| case extensions::UnloadedExtensionReason::PROFILE_SHUTDOWN: |
| case extensions::UnloadedExtensionReason::LOCK_ALL: |
| case extensions::UnloadedExtensionReason::MIGRATED_TO_COMPONENT: |
| return; |
| } |
| Publish(MakeApp(extension, readiness)); |
| } |
| |
| void OnExtensionInstalled(content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| bool is_update) override { |
| if (!which_type_.Matches(extension)) |
| return; |
| Publish(MakeApp(extension, Readiness::kReady)); |
| } |
| |
| void OnExtensionUninstalled(content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| extensions::UninstallReason reason) override { |
| if (!which_type_.Matches(extension)) |
| return; |
| apps::AppPtr app = |
| MakeApp(extension, reason == extensions::UNINSTALL_REASON_MIGRATED |
| ? Readiness::kUninstalledByNonUser |
| : Readiness::kUninstalledByUser); |
| Publish(std::move(app)); |
| } |
| |
| void OnShutdown(extensions::ExtensionRegistry* registry) override { |
| registry_observation_.Reset(); |
| } |
| |
| // AppWindowRegistry::Observer overrides. |
| void OnAppWindowAdded(extensions::AppWindow* app_window) override { |
| // Only chrome app windows are added to the dock. |
| if (!which_type_.IsChromeApps()) |
| return; |
| // The extension also has to match. |
| if (!which_type_.Matches(app_window->GetExtension())) |
| return; |
| std::string window_id = lacros_window_utility::GetRootWindowUniqueId( |
| app_window->GetNativeWindow()); |
| app_window_id_cache_[app_window] = window_id; |
| |
| publisher_->OnAppWindowAdded(app_window->GetExtension()->id(), window_id); |
| } |
| |
| void OnAppWindowRemoved(extensions::AppWindow* app_window) override { |
| // Only chrome app windows are added to the dock. |
| if (!which_type_.IsChromeApps()) |
| return; |
| // The extension also has to match. As the extension may be destroyed at |
| // this point, we use presence in app_window_id_cache_ to decide whether to |
| // continue. |
| auto it = app_window_id_cache_.find(app_window); |
| if (it == app_window_id_cache_.end()) |
| return; |
| |
| std::string window_id = it->second; |
| publisher_->OnAppWindowRemoved(app_window->extension_id(), window_id); |
| |
| app_window_id_cache_.erase(app_window); |
| } |
| |
| // Publishes a differential update to the app service. |
| void Publish(apps::AppPtr app) { |
| std::vector<apps::AppPtr> apps; |
| apps.push_back(std::move(app)); |
| Publish(std::move(apps)); |
| } |
| |
| // Publishes a vector of differential updates to the app service. |
| void Publish(std::vector<apps::AppPtr> apps) { |
| publisher_->Publish(std::move(apps)); |
| } |
| |
| // Whether the app should be shown in the launcher, shelf, etc. |
| bool ShouldShow(const extensions::Extension* extension) { |
| if (which_type_.IsExtensions()) |
| return false; |
| extensions::ExtensionRegistry* registry = |
| extensions::ExtensionRegistry::Get(profile_); |
| const std::string& app_id = extension->id(); |
| // These three extension sets are the same three consulted by the |
| // constructor. Importantly, it will exclude previously installed but |
| // currently uninstalled extensions. |
| bool connected = registry->enabled_extensions().Contains(app_id) || |
| registry->disabled_extensions().Contains(app_id) || |
| registry->terminated_extensions().Contains(app_id); |
| if (!connected) |
| return false; |
| |
| return extensions::ui_util::ShouldDisplayInAppLauncher(extension, profile_); |
| } |
| |
| // Creates an AppPtr from an extension. |
| apps::AppPtr MakeApp(const extensions::Extension* extension, |
| Readiness readiness) { |
| DCHECK(which_type_.Matches(extension)); |
| apps::AppType app_type = which_type_.ChooseForChromeAppOrExtension( |
| apps::AppType::kStandaloneBrowserChromeApp, |
| apps::AppType::kStandaloneBrowserExtension); |
| auto app = std::make_unique<apps::App>(app_type, extension->id()); |
| |
| const bool is_app_disabled = |
| base::Contains(disabled_apps_, extension->id()); |
| app->readiness = is_app_disabled ? Readiness::kDisabledByPolicy : readiness; |
| app->name = extension->name(); |
| app->short_name = extension->short_name(); |
| app->installer_package_id = |
| apps::PackageId(apps::PackageType::kChromeApp, extension->id()); |
| |
| // TODO(crbug.com/40240007): Work out how pinning interacts with Lacros |
| // multi-profile support once there is a product decision on what that looks |
| // like. |
| app->policy_ids = {extension->id()}; |
| |
| app->icon_key = apps::IconKey(GetIconEffects(extension)); |
| |
| auto* prefs = extensions::ExtensionPrefs::Get(profile_); |
| if (prefs) { |
| app->last_launch_time = prefs->GetLastLaunchTime(extension->id()); |
| app->install_time = prefs->GetLastUpdateTime(extension->id()); |
| } else { |
| app->last_launch_time = base::Time(); |
| app->install_time = base::Time(); |
| } |
| |
| app->install_reason = GetInstallReason(extension); |
| app->recommendable = true; |
| app->searchable = true; |
| app->paused = false; |
| |
| if (is_app_disabled && is_disabled_apps_mode_hidden_) { |
| app->show_in_launcher = false; |
| app->show_in_search = false; |
| app->show_in_shelf = false; |
| app->handles_intents = false; |
| app->show_in_management = false; |
| } else { |
| bool show = ShouldShow(extension); |
| app->show_in_launcher = show; |
| app->show_in_shelf = show; |
| app->show_in_search = show; |
| app->show_in_management = |
| extensions::AppDisplayInfo::ShouldDisplayInAppLauncher(*extension); |
| app->handles_intents = which_type_.IsExtensions() || show; |
| } |
| |
| if (which_type_.IsChromeApps()) { |
| app->is_platform_app = extension->is_platform_app(); |
| |
| if (extension->is_hosted_app()) { |
| app->window_mode = |
| extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_), |
| extension) == |
| extensions::LaunchType::LAUNCH_TYPE_WINDOW |
| ? apps::WindowMode::kWindow |
| : apps::WindowMode::kBrowser; |
| } |
| } |
| |
| const extensions::ManagementPolicy* policy = |
| extensions::ExtensionSystem::Get(profile_)->management_policy(); |
| app->allow_uninstall = (policy->UserMayModifySettings(extension, nullptr) && |
| !policy->MustRemainInstalled(extension, nullptr)); |
| |
| app->allow_close = true; |
| |
| // Add file_handlers for either of the following: |
| // a) Chrome Apps and quickoffice. |
| // b) Web File Handlers or file_browser_handler for Extensions. |
| base::Extend(app->intent_filters, |
| which_type_.ChooseIntentFilter( |
| extensions::IsLegacyQuickOfficeExtension(*extension), |
| apps_util::CreateIntentFiltersForChromeApp, |
| apps_util::CreateIntentFiltersForExtension)(extension)); |
| return app; |
| } |
| |
| apps::IconEffects GetIconEffects(const extensions::Extension* extension) { |
| apps::IconEffects icon_effects = apps::IconEffects::kNone; |
| icon_effects = static_cast<apps::IconEffects>( |
| icon_effects | apps::IconEffects::kCrOsStandardIcon); |
| |
| if (base::Contains(disabled_apps_, extension->id())) { |
| icon_effects = static_cast<apps::IconEffects>( |
| icon_effects | apps::IconEffects::kBlocked); |
| } |
| return icon_effects; |
| } |
| |
| void OnSystemFeaturesPrefChanged() { |
| PrefService* const local_state = g_browser_process->local_state(); |
| if (!local_state || !local_state->FindPreference( |
| policy::policy_prefs::kSystemFeaturesDisableList)) { |
| return; |
| } |
| |
| const base::Value::List& disabled_system_features = |
| local_state->GetList(policy::policy_prefs::kSystemFeaturesDisableList); |
| |
| const bool is_pref_disabled_mode_hidden = |
| local_state->GetString( |
| policy::policy_prefs::kSystemFeaturesDisableMode) == |
| policy::kHiddenDisableMode; |
| const bool is_disabled_mode_changed = |
| (is_pref_disabled_mode_hidden != is_disabled_apps_mode_hidden_); |
| is_disabled_apps_mode_hidden_ = is_pref_disabled_mode_hidden; |
| |
| UpdateAppDisabledState(disabled_system_features, |
| static_cast<int>(policy::SystemFeature::kWebStore), |
| extensions::kWebStoreAppId, |
| is_disabled_mode_changed); |
| } |
| |
| void UpdateAppDisabledState( |
| const base::Value::List& disabled_system_features_pref, |
| int feature, |
| const std::string& app_id, |
| bool is_disabled_mode_changed) { |
| const bool is_disabled = |
| base::Contains(disabled_system_features_pref, base::Value(feature)); |
| // Sometimes the policy is updated before the app is installed, so this way |
| // the disabled_apps_ is updated regardless the Publish should happen or not |
| // and the app will be published with the correct readiness upon its |
| // installation. |
| const bool should_publish = |
| (base::Contains(disabled_apps_, app_id) != is_disabled) || |
| is_disabled_mode_changed; |
| |
| if (is_disabled) { |
| disabled_apps_.insert(app_id); |
| } else { |
| disabled_apps_.erase(app_id); |
| } |
| |
| if (!should_publish) { |
| return; |
| } |
| |
| const auto* extension = |
| lacros_extensions_util::MaybeGetExtension(profile_, app_id); |
| if (!extension) { |
| return; |
| } |
| |
| Publish(extension, |
| is_disabled ? Readiness::kDisabledByPolicy : Readiness::kReady); |
| } |
| |
| // This pointer is guaranteed to be valid and to outlive this object. |
| const raw_ptr<Profile> profile_; |
| |
| // This pointer is guaranteed to be valid and to outlive this object. |
| const raw_ptr<LacrosExtensionAppsPublisher> publisher_; |
| |
| // State to decide which extension type (e.g., Chrome Apps vs. Extensions) |
| // to support. |
| const ForWhichExtensionType which_type_; |
| |
| // Tracks apps that have been disabled from installing by enterprise policy. |
| // The values come from local state and are set by updating the |
| // SystemFeaturesDisableList policy. |
| std::set<std::string> disabled_apps_; |
| |
| // Boolean signifying whether the preferred user experience mode of disabled |
| // apps is hidden (true) or blocked (false). The value comes from local state |
| // and is set by updating the SystemFeaturesDisableMode policy. |
| bool is_disabled_apps_mode_hidden_ = false; |
| |
| // Registrar used to monitor the local state prefs. |
| PrefChangeRegistrar local_state_pref_change_registrar_; |
| |
| // Observes both extension prefs and registry for events that affect |
| // extensions. |
| base::ScopedObservation<extensions::ExtensionPrefs, |
| extensions::ExtensionPrefsObserver> |
| prefs_observation_{this}; |
| base::ScopedObservation<extensions::ExtensionRegistry, |
| extensions::ExtensionRegistryObserver> |
| registry_observation_{this}; |
| |
| // Observes AppWindowRegistry for app window creation and destruction. |
| base::ScopedObservation<extensions::AppWindowRegistry, |
| extensions::AppWindowRegistry::Observer> |
| app_window_registry_observation_{this}; |
| |
| // Records the window id associated with an app window. This is needed since |
| // the app window destruction callback occurs after the window is destroyed. |
| std::map<extensions::AppWindow*, std::string> app_window_id_cache_; |
| |
| base::WeakPtrFactory<LacrosExtensionAppsPublisher::ProfileTracker> |
| weak_factory_{this}; |
| }; |
| |
| // static |
| std::unique_ptr<LacrosExtensionAppsPublisher> |
| LacrosExtensionAppsPublisher::MakeForChromeApps() { |
| return std::make_unique<LacrosExtensionAppsPublisher>(InitForChromeApps()); |
| } |
| |
| // static |
| std::unique_ptr<LacrosExtensionAppsPublisher> |
| LacrosExtensionAppsPublisher::MakeForExtensions() { |
| return std::make_unique<LacrosExtensionAppsPublisher>(InitForExtensions()); |
| } |
| |
| LacrosExtensionAppsPublisher::LacrosExtensionAppsPublisher( |
| const ForWhichExtensionType& which_type) |
| : which_type_(which_type) {} |
| |
| LacrosExtensionAppsPublisher::~LacrosExtensionAppsPublisher() = default; |
| |
| void LacrosExtensionAppsPublisher::Initialize() { |
| if (!InitializeCrosapi()) |
| return; |
| profile_manager_observation_.Observe(g_browser_process->profile_manager()); |
| auto profiles = g_browser_process->profile_manager()->GetLoadedProfiles(); |
| for (auto* profile : profiles) { |
| // TODO(crbug.com/40199791): The app id is not stable for secondary |
| // profiles and cannot be stored in sync. Thus, the app cannot be published |
| // at all. |
| if (!profile->IsMainProfile()) |
| continue; |
| profile_trackers_[profile] = |
| std::make_unique<ProfileTracker>(profile, this, which_type_); |
| } |
| |
| // Only track the media usage for the chrome apps. |
| if (which_type_.IsChromeApps()) { |
| media_dispatcher_.Observe(MediaCaptureDevicesDispatcher::GetInstance() |
| ->GetMediaStreamCaptureIndicator() |
| .get()); |
| } |
| } |
| |
| bool LacrosExtensionAppsPublisher::InitializeCrosapi() { |
| // Ash is too old to support the chrome app publisher interface. |
| int crosapiVersion = chromeos::LacrosService::Get() |
| ->GetInterfaceVersion<crosapi::mojom::Crosapi>(); |
| int minRequiredVersion = |
| static_cast<int>(which_type_.ChooseForChromeAppOrExtension( |
| crosapi::mojom::Crosapi::kBindChromeAppPublisherMinVersion, |
| crosapi::mojom::Crosapi::kBindExtensionPublisherMinVersion)); |
| if (crosapiVersion < minRequiredVersion) |
| return false; |
| |
| // Ash is too old to support the chrome app window tracker interface. |
| if (!chromeos::LacrosService::Get() |
| ->IsAvailable<crosapi::mojom::AppWindowTracker>()) { |
| return false; |
| } |
| |
| if (which_type_.IsChromeApps()) { |
| chromeos::LacrosService::Get() |
| ->BindPendingReceiverOrRemote< |
| mojo::PendingReceiver<crosapi::mojom::AppPublisher>, |
| &crosapi::mojom::Crosapi::BindChromeAppPublisher>( |
| publisher_.BindNewPipeAndPassReceiver()); |
| } else if (which_type_.IsExtensions()) { |
| chromeos::LacrosService::Get() |
| ->BindPendingReceiverOrRemote< |
| mojo::PendingReceiver<crosapi::mojom::AppPublisher>, |
| &crosapi::mojom::Crosapi::BindExtensionPublisher>( |
| publisher_.BindNewPipeAndPassReceiver()); |
| } |
| return true; |
| } |
| |
| void LacrosExtensionAppsPublisher::Publish(std::vector<apps::AppPtr> apps) { |
| publisher_->OnApps(std::move(apps)); |
| } |
| |
| void LacrosExtensionAppsPublisher::PublishCapabilityAccesses( |
| std::vector<apps::CapabilityAccessPtr> accesses) { |
| publisher_->OnCapabilityAccesses(std::move(accesses)); |
| } |
| |
| void LacrosExtensionAppsPublisher::OnAppWindowAdded( |
| const std::string& app_id, |
| const std::string& window_id) { |
| chromeos::LacrosService::Get() |
| ->GetRemote<crosapi::mojom::AppWindowTracker>() |
| ->OnAppWindowAdded(app_id, window_id); |
| } |
| |
| void LacrosExtensionAppsPublisher::OnAppWindowRemoved( |
| const std::string& app_id, |
| const std::string& window_id) { |
| chromeos::LacrosService::Get() |
| ->GetRemote<crosapi::mojom::AppWindowTracker>() |
| ->OnAppWindowRemoved(app_id, window_id); |
| } |
| |
| void LacrosExtensionAppsPublisher::OnProfileAdded(Profile* profile) { |
| // TODO(crbug.com/40199791): The app id is not stable for secondary |
| // profiles and cannot be stored in sync. Thus, the app cannot be published |
| // at all. |
| if (!profile->IsMainProfile()) |
| return; |
| profile_trackers_[profile] = |
| std::make_unique<ProfileTracker>(profile, this, which_type_); |
| } |
| |
| void LacrosExtensionAppsPublisher::OnProfileMarkedForPermanentDeletion( |
| Profile* profile) { |
| profile_trackers_.erase(profile); |
| } |
| |
| void LacrosExtensionAppsPublisher::OnProfileManagerDestroying() { |
| profile_trackers_.clear(); |
| profile_manager_observation_.Reset(); |
| } |
| |
| void LacrosExtensionAppsPublisher::UpdateAppWindowMode( |
| const std::string& app_id, |
| apps::WindowMode window_mode) { |
| Profile* profile = nullptr; |
| const extensions::Extension* extension = nullptr; |
| bool success = lacros_extensions_util::GetProfileAndExtension( |
| app_id, &profile, &extension); |
| if (!success) |
| return; |
| |
| DCHECK(extension->is_hosted_app()); |
| |
| // Persist hosted app's launch preference. |
| extensions::SetLaunchType(profile, extension->id(), |
| window_mode == apps::WindowMode::kWindow |
| ? extensions::LAUNCH_TYPE_WINDOW |
| : extensions::LAUNCH_TYPE_REGULAR); |
| |
| // Republish the app. |
| auto matched = profile_trackers_.find(profile); |
| CHECK(matched != profile_trackers_.end(), base::NotFatalUntil::M130); |
| matched->second->Publish(extension, apps::Readiness::kReady); |
| } |
| |
| void LacrosExtensionAppsPublisher::UpdateAppSize(const std::string& app_id) { |
| Profile* profile = nullptr; |
| const extensions::Extension* extension = nullptr; |
| bool success = lacros_extensions_util::GetProfileAndExtension( |
| app_id, &profile, &extension); |
| if (!success) { |
| return; |
| } |
| |
| extensions::path_util::CalculateExtensionDirectorySize( |
| extension->path(), |
| base::BindOnce(&LacrosExtensionAppsPublisher::OnSizeCalculated, |
| weak_ptr_factory_.GetWeakPtr(), extension->id())); |
| } |
| |
| void LacrosExtensionAppsPublisher::OnIsCapturingVideoChanged( |
| content::WebContents* web_contents, |
| bool is_capturing_video) { |
| auto app_id = MaybeGetAppId(web_contents); |
| if (!app_id.has_value()) { |
| return; |
| } |
| |
| auto result = media_requests_.UpdateCameraState(app_id.value(), web_contents, |
| is_capturing_video); |
| ModifyCapabilityAccess(app_id.value(), result.camera, result.microphone); |
| } |
| |
| void LacrosExtensionAppsPublisher::OnIsCapturingAudioChanged( |
| content::WebContents* web_contents, |
| bool is_capturing_audio) { |
| auto app_id = MaybeGetAppId(web_contents); |
| if (!app_id.has_value()) { |
| return; |
| } |
| |
| auto result = media_requests_.UpdateMicrophoneState( |
| app_id.value(), web_contents, is_capturing_audio); |
| ModifyCapabilityAccess(app_id.value(), result.camera, result.microphone); |
| } |
| |
| void LacrosExtensionAppsPublisher::OnSizeCalculated(const std::string& app_id, |
| int64_t size) { |
| std::vector<apps::AppPtr> apps; |
| apps::AppType app_type = which_type_.ChooseForChromeAppOrExtension( |
| apps::AppType::kStandaloneBrowserChromeApp, |
| apps::AppType::kStandaloneBrowserExtension); |
| auto app = std::make_unique<apps::App>(app_type, app_id); |
| app->app_size_in_bytes = size; |
| apps.push_back(std::move(app)); |
| Publish(std::move(apps)); |
| } |
| |
| std::optional<std::string> LacrosExtensionAppsPublisher::MaybeGetAppId( |
| content::WebContents* web_contents) { |
| // The web app publisher is responsible to handle `web_contents` for web |
| // apps. |
| const webapps::AppId* web_app_id = |
| web_app::WebAppTabHelper::GetAppId(web_contents); |
| if (web_app_id) { |
| return std::nullopt; |
| } |
| |
| const auto* extension = |
| lacros_extensions_util::MaybeGetExtension(web_contents); |
| return (extension && which_type_.Matches(extension)) |
| ? std::make_optional<std::string>(extension->id()) |
| : std::nullopt; |
| } |
| |
| void LacrosExtensionAppsPublisher::ModifyCapabilityAccess( |
| const std::string& app_id, |
| std::optional<bool> accessing_camera, |
| std::optional<bool> accessing_microphone) { |
| if (!accessing_camera.has_value() && !accessing_microphone.has_value()) { |
| return; |
| } |
| |
| std::vector<apps::CapabilityAccessPtr> capability_accesses; |
| auto capability_access = std::make_unique<apps::CapabilityAccess>(app_id); |
| capability_access->camera = accessing_camera; |
| capability_access->microphone = accessing_microphone; |
| capability_accesses.push_back(std::move(capability_access)); |
| |
| PublishCapabilityAccesses(std::move(capability_accesses)); |
| } |