| // Copyright 2012 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/background/background_application_list_model.h" |
| |
| #include <algorithm> |
| #include <set> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/observer_list.h" |
| #include "base/one_shot_event.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/background/background_contents_service.h" |
| #include "chrome/browser/background/background_contents_service_factory.h" |
| #include "chrome/browser/background/background_mode_manager.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "components/crx_file/id_util.h" |
| #include "extensions/browser/extension_host.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/image_loader.h" |
| #include "extensions/browser/permissions_manager.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_icon_set.h" |
| #include "extensions/common/extension_resource.h" |
| #include "extensions/common/extension_set.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "extensions/common/manifest_handlers/icons_handler.h" |
| #include "extensions/common/permissions/permission_set.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "ui/base/l10n/l10n_util_collator.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/image/image_skia.h" |
| |
| using extensions::APIPermission; |
| using extensions::Extension; |
| using extensions::ExtensionList; |
| using extensions::ExtensionRegistry; |
| using extensions::ExtensionSet; |
| using extensions::PermissionSet; |
| using extensions::UnloadedExtensionReason; |
| using extensions::mojom::APIPermissionID; |
| |
| class ExtensionNameComparator { |
| public: |
| bool operator()(const scoped_refptr<const Extension>& x, |
| const scoped_refptr<const Extension>& y) { |
| return x->name() < y->name(); |
| } |
| }; |
| |
| // Background application representation, private to the |
| // BackgroundApplicationListModel class. |
| class BackgroundApplicationListModel::Application final { |
| public: |
| Application(BackgroundApplicationListModel* model, |
| const Extension* an_extension); |
| |
| virtual ~Application(); |
| |
| // Invoked when a request icon is available. |
| void OnImageLoaded(const gfx::Image& image); |
| |
| // Uses the FILE thread to request this extension's icon, sized |
| // appropriately. |
| void RequestIcon(extension_misc::ExtensionIcons size); |
| |
| raw_ptr<const Extension> extension_; |
| gfx::ImageSkia icon_; |
| raw_ptr<BackgroundApplicationListModel> model_; |
| |
| private: |
| base::WeakPtrFactory<Application> weak_ptr_factory_{this}; |
| }; |
| |
| namespace { |
| void GetServiceApplications(extensions::ExtensionService* service, |
| ExtensionList* applications_result) { |
| ExtensionRegistry* registry = ExtensionRegistry::Get(service->profile()); |
| const ExtensionSet& enabled_extensions = registry->enabled_extensions(); |
| |
| auto* process_manager = extensions::ProcessManager::Get(service->profile()); |
| |
| for (const auto& extension : enabled_extensions) { |
| if (BackgroundApplicationListModel::IsPersistentBackgroundApp( |
| *extension, service->profile()) || |
| (BackgroundApplicationListModel::IsTransientBackgroundApp( |
| *extension, service->profile()) && |
| process_manager->GetBackgroundHostForExtension(extension->id()))) { |
| applications_result->push_back(extension); |
| } |
| } |
| |
| // Walk the list of terminated extensions also (just because an extension |
| // crashed doesn't mean we should ignore it). |
| const ExtensionSet& terminated_extensions = registry->terminated_extensions(); |
| for (const auto& extension : terminated_extensions) { |
| if (BackgroundApplicationListModel::IsPersistentBackgroundApp( |
| *extension, service->profile())) { |
| applications_result->push_back(extension); |
| } |
| } |
| |
| std::sort(applications_result->begin(), applications_result->end(), |
| ExtensionNameComparator()); |
| } |
| |
| } // namespace |
| |
| void BackgroundApplicationListModel::Observer::OnApplicationDataChanged() {} |
| |
| void BackgroundApplicationListModel::Observer::OnApplicationListChanged( |
| const Profile* profile) {} |
| |
| BackgroundApplicationListModel::Observer::~Observer() { |
| } |
| |
| BackgroundApplicationListModel::Application::~Application() { |
| } |
| |
| BackgroundApplicationListModel::Application::Application( |
| BackgroundApplicationListModel* model, |
| const Extension* extension) |
| : extension_(extension), model_(model) {} |
| |
| void BackgroundApplicationListModel::Application::OnImageLoaded( |
| const gfx::Image& image) { |
| if (image.IsEmpty()) |
| return; |
| icon_ = image.AsImageSkia(); |
| model_->SendApplicationDataChangedNotifications(); |
| } |
| |
| void BackgroundApplicationListModel::Application::RequestIcon( |
| extension_misc::ExtensionIcons size) { |
| extensions::ExtensionResource resource = |
| extensions::IconsInfo::GetIconResource( |
| extension_, size, ExtensionIconSet::MATCH_BIGGER); |
| extensions::ImageLoader::Get(model_->profile_) |
| ->LoadImageAsync(extension_, resource, gfx::Size(size, size), |
| base::BindOnce(&Application::OnImageLoaded, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| BackgroundApplicationListModel::~BackgroundApplicationListModel() = default; |
| |
| BackgroundApplicationListModel::BackgroundApplicationListModel(Profile* profile) |
| : profile_(profile) { |
| DCHECK(profile_); |
| extensions::ExtensionSystem::Get(profile_)->ready().Post( |
| FROM_HERE, |
| base::BindOnce(&BackgroundApplicationListModel::OnExtensionSystemReady, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BackgroundApplicationListModel::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void BackgroundApplicationListModel::AssociateApplicationData( |
| const Extension* extension) { |
| DCHECK(IsBackgroundApp(*extension, profile_)); |
| Application* application = FindApplication(extension); |
| if (!application) { |
| // App position is used as a dynamic command and so must be less than any |
| // predefined command id. |
| if (applications_.size() >= IDC_MinimumLabelValue) { |
| LOG(ERROR) << "Background application limit of " << IDC_MinimumLabelValue |
| << " exceeded. Ignoring."; |
| return; |
| } |
| std::unique_ptr<Application> application_ptr = |
| std::make_unique<Application>(this, extension); |
| application = application_ptr.get(); |
| applications_[extension->id()] = std::move(application_ptr); |
| application->RequestIcon(extension_misc::EXTENSION_ICON_BITTY); |
| } |
| } |
| |
| void BackgroundApplicationListModel::DissociateApplicationData( |
| const Extension* extension) { |
| applications_.erase(extension->id()); |
| } |
| |
| const Extension* BackgroundApplicationListModel::GetExtension( |
| int position) const { |
| DCHECK(position >= 0 && static_cast<size_t>(position) < extensions_.size()); |
| return extensions_[position].get(); |
| } |
| |
| const BackgroundApplicationListModel::Application* |
| BackgroundApplicationListModel::FindApplication( |
| const Extension* extension) const { |
| const std::string& id = extension->id(); |
| auto found = applications_.find(id); |
| return (found == applications_.end()) ? nullptr : found->second.get(); |
| } |
| |
| BackgroundApplicationListModel::Application* |
| BackgroundApplicationListModel::FindApplication( |
| const Extension* extension) { |
| const std::string& id = extension->id(); |
| auto found = applications_.find(id); |
| return (found == applications_.end()) ? nullptr : found->second.get(); |
| } |
| |
| gfx::ImageSkia BackgroundApplicationListModel::GetIcon( |
| const Extension* extension) { |
| const Application* application = FindApplication(extension); |
| if (application) |
| return application->icon_; |
| AssociateApplicationData(extension); |
| return gfx::ImageSkia(); |
| } |
| |
| int BackgroundApplicationListModel::GetPosition( |
| const Extension* extension) const { |
| int position = 0; |
| const std::string& id = extension->id(); |
| for (const auto& it : extensions_) { |
| if (id == it->id()) |
| return position; |
| ++position; |
| } |
| NOTREACHED(); |
| return -1; |
| } |
| |
| // static |
| bool BackgroundApplicationListModel::IsPersistentBackgroundApp( |
| const Extension& extension, |
| Profile* profile) { |
| // An extension is a "background app" if it has the "background API" |
| // permission, and meets one of the following criteria: |
| // 1) It is an extension (not a hosted app). |
| // 2) It is a hosted app, and has a background contents registered or in the |
| // manifest. |
| |
| // Not a background app if we don't have the background permission. |
| if (!extension.permissions_data()->HasAPIPermission( |
| APIPermissionID::kBackground)) { |
| return false; |
| } |
| |
| // Extensions and packaged apps with background permission are always treated |
| // as background apps. |
| if (!extension.is_hosted_app()) |
| return true; |
| |
| // Hosted apps with manifest-provided background pages are background apps. |
| if (extensions::BackgroundInfo::HasBackgroundPage(&extension)) |
| return true; |
| |
| BackgroundContentsService* service = |
| BackgroundContentsServiceFactory::GetForProfile(profile); |
| // If we have an active or registered background contents for this app, then |
| // it's a background app. This covers the cases where the app has created its |
| // background contents, but it hasn't navigated yet, or the background |
| // contents crashed and hasn't yet been restarted - in both cases we still |
| // want to treat the app as a background app. |
| if (service->GetAppBackgroundContents(extension.id()) || |
| service->HasRegisteredBackgroundContents(extension.id())) { |
| return true; |
| } |
| |
| // Doesn't meet our criteria, so it's not a background app. |
| return false; |
| } |
| |
| // static |
| bool BackgroundApplicationListModel::IsTransientBackgroundApp( |
| const Extension& extension, |
| Profile* profile) { |
| return base::FeatureList::IsEnabled(features::kOnConnectNative) && |
| extension.permissions_data()->HasAPIPermission( |
| APIPermissionID::kTransientBackground) && |
| extensions::BackgroundInfo::HasLazyBackgroundPage(&extension); |
| } |
| |
| bool BackgroundApplicationListModel::HasPersistentBackgroundApps() const { |
| for (auto& extension : extensions_) { |
| if (IsPersistentBackgroundApp(*extension, profile_)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void BackgroundApplicationListModel::SendApplicationDataChangedNotifications() { |
| for (auto& observer : observers_) |
| observer.OnApplicationDataChanged(); |
| } |
| |
| void BackgroundApplicationListModel::OnExtensionLoaded( |
| content::BrowserContext* browser_context, |
| const Extension* extension) { |
| // We only care about extensions that are background applications. |
| if (!IsBackgroundApp(*extension, profile_)) |
| return; |
| Update(); |
| AssociateApplicationData(extension); |
| } |
| |
| void BackgroundApplicationListModel::OnExtensionUnloaded( |
| content::BrowserContext* browser_context, |
| const Extension* extension, |
| UnloadedExtensionReason reason) { |
| if (!IsBackgroundApp(*extension, profile_)) |
| return; |
| Update(); |
| DissociateApplicationData(extension); |
| } |
| |
| void BackgroundApplicationListModel::OnExtensionSystemReady() { |
| // All initial extensions will be loaded when extension system ready. So we |
| // can get everything here. |
| Update(); |
| for (const auto& extension : extensions_) |
| AssociateApplicationData(extension.get()); |
| |
| // If we register for extension loaded notifications in the ctor, we need to |
| // know that this object is constructed prior to the initialization process |
| // for the extension system, which isn't a guarantee. Thus, register here and |
| // associate all initial extensions. |
| extension_registry_observation_.Observe(ExtensionRegistry::Get(profile_)); |
| |
| background_contents_service_observation_.Observe( |
| BackgroundContentsServiceFactory::GetForProfile(profile_)); |
| |
| if (base::FeatureList::IsEnabled(features::kOnConnectNative)) { |
| process_manager_observation_.Observe( |
| extensions::ProcessManager::Get(profile_)); |
| } |
| |
| permissions_manager_observation_.Observe( |
| extensions::PermissionsManager::Get(profile_)); |
| |
| startup_done_ = true; |
| } |
| |
| void BackgroundApplicationListModel::OnShutdown(ExtensionRegistry* registry) { |
| DCHECK_EQ(ExtensionRegistry::Get(profile_), registry); |
| DCHECK(extension_registry_observation_.IsObservingSource(registry)); |
| extension_registry_observation_.Reset(); |
| process_manager_observation_.Reset(); |
| permissions_manager_observation_.Reset(); |
| } |
| |
| void BackgroundApplicationListModel::OnBackgroundContentsServiceChanged() { |
| Update(); |
| } |
| |
| void BackgroundApplicationListModel::OnBackgroundContentsServiceDestroying() { |
| background_contents_service_observation_.Reset(); |
| } |
| |
| void BackgroundApplicationListModel::OnExtensionPermissionsUpdated( |
| const extensions::Extension& extension, |
| const extensions::PermissionSet& permissions, |
| extensions::PermissionsManager::UpdateReason reason) { |
| if (permissions.HasAPIPermission(APIPermissionID::kBackground) || |
| (base::FeatureList::IsEnabled(features::kOnConnectNative) && |
| permissions.HasAPIPermission(APIPermissionID::kTransientBackground))) { |
| switch (reason) { |
| case extensions::PermissionsManager::UpdateReason::kAdded: |
| case extensions::PermissionsManager::UpdateReason::kRemoved: |
| Update(); |
| if (IsBackgroundApp(extension, profile_)) { |
| AssociateApplicationData(&extension); |
| } else { |
| DissociateApplicationData(&extension); |
| } |
| break; |
| case extensions::PermissionsManager::UpdateReason::kPolicy: |
| // Policy changes are only used for host permissions, so the |
| // "background" |
| // permission would never be present in permissions . |
| NOTREACHED(); |
| } |
| } |
| } |
| |
| void BackgroundApplicationListModel::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| // Update queries the extensions service of the profile with which the model was |
| // initialized to determine the current set of background applications. If that |
| // differs from the old list, it generates OnApplicationListChanged events for |
| // each observer. |
| void BackgroundApplicationListModel::Update() { |
| extensions::ExtensionSystem* extension_system = |
| extensions::ExtensionSystem::Get(profile_); |
| DCHECK(extension_system->is_ready()); |
| |
| // Discover current background applications, compare with previous list, which |
| // is consistently sorted, and notify observers if they differ. |
| ExtensionList extensions; |
| GetServiceApplications(extension_system->extension_service(), &extensions); |
| ExtensionList::const_iterator old_cursor = extensions_.begin(); |
| ExtensionList::const_iterator new_cursor = extensions.begin(); |
| while (old_cursor != extensions_.end() && |
| new_cursor != extensions.end() && |
| (*old_cursor)->name() == (*new_cursor)->name() && |
| (*old_cursor)->id() == (*new_cursor)->id()) { |
| ++old_cursor; |
| ++new_cursor; |
| } |
| if (old_cursor != extensions_.end() || new_cursor != extensions.end()) { |
| extensions_ = extensions; |
| for (auto& observer : observers_) |
| observer.OnApplicationListChanged(profile_); |
| } |
| } |
| |
| void BackgroundApplicationListModel::OnBackgroundHostCreated( |
| extensions::ExtensionHost* host) { |
| if (IsTransientBackgroundApp(*host->extension(), profile_)) { |
| Update(); |
| } |
| } |
| |
| void BackgroundApplicationListModel::OnBackgroundHostClose( |
| const std::string& extension_id) { |
| auto* extension = |
| ExtensionRegistry::Get(profile_)->GetInstalledExtension(extension_id); |
| |
| if (!extension || !IsTransientBackgroundApp(*extension, profile_)) { |
| return; |
| } |
| |
| Update(); |
| } |