| // 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/extensions/component_loader.h" |
| |
| #include <optional> |
| #include <string> |
| |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/json_string_value_serializer.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/path_service.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/values.h" |
| #include "build/branding_buildflags.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/extensions/component_extensions_allowlist/allowlist.h" |
| #include "chrome/browser/extensions/data_deleter.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/launch_util.h" |
| #include "chrome/browser/extensions/profile_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/channel_info.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/grit/branded_strings.h" |
| #include "chrome/grit/browser_resources.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/crx_file/id_util.h" |
| #include "components/nacl/common/buildflags.h" |
| #include "components/version_info/version_info.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/content_switches.h" |
| #include "extensions/browser/extension_file_task_runner.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/pref_names.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_features.h" |
| #include "extensions/common/extension_id.h" |
| #include "extensions/common/extension_l10n_util.h" |
| #include "extensions/common/file_util.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "pdf/buildflags.h" |
| #include "printing/buildflags/buildflags.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/constants/ash_switches.h" |
| #include "ash/keyboard/ui/grit/keyboard_resources.h" |
| #include "base/system/sys_info.h" |
| #include "chrome/browser/ash/crosapi/browser_util.h" |
| #include "chrome/browser/ash/file_manager/app_id.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "extensions/browser/extensions_browser_client.h" |
| #include "extensions/common/switches.h" |
| #include "storage/browser/file_system/file_system_context.h" |
| #include "ui/accessibility/accessibility_features.h" |
| #include "ui/file_manager/grit/file_manager_resources.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chromeos/constants/chromeos_features.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_PDF) |
| #include "chrome/browser/pdf/pdf_extension_util.h" |
| #endif |
| |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| #include "chrome/browser/defaults.h" |
| #endif |
| |
| using content::BrowserThread; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| bool g_enable_background_extensions_during_testing = false; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| // Whether HelpApp is enabled. |
| bool g_enable_help_app = true; |
| #endif |
| |
| ExtensionId GenerateId(const base::Value::Dict& manifest, |
| const base::FilePath& path) { |
| std::string id_input; |
| const std::string* raw_key = manifest.FindString(manifest_keys::kPublicKey); |
| CHECK(raw_key != nullptr); |
| CHECK(Extension::ParsePEMKeyBytes(*raw_key, &id_input)); |
| ExtensionId id = crx_file::id_util::GenerateId(id_input); |
| return id; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| std::optional<base::Value::Dict> LoadManifestOnFileThread( |
| const base::FilePath& root_directory, |
| const base::FilePath::CharType* manifest_filename, |
| bool localize_manifest) { |
| DCHECK(GetExtensionFileTaskRunner()->RunsTasksInCurrentSequence()); |
| std::string error; |
| std::optional<base::Value::Dict> manifest( |
| file_util::LoadManifest(root_directory, manifest_filename, &error)); |
| if (!manifest) { |
| LOG(ERROR) << "Can't load " |
| << root_directory.Append(manifest_filename).AsUTF8Unsafe() |
| << ": " << error; |
| return std::nullopt; |
| } |
| |
| if (localize_manifest) { |
| // This is only called for Chrome OS component extensions which are loaded |
| // from a read-only rootfs partition, so it is safe to set |
| // |gzip_permission| to kAllowForTrustedSource. |
| bool localized = extension_l10n_util::LocalizeExtension( |
| root_directory, &manifest.value(), |
| extension_l10n_util::GzippedMessagesPermission::kAllowForTrustedSource, |
| &error); |
| CHECK(localized) << error; |
| } |
| |
| return manifest; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| bool IsNormalSession() { |
| return !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| ash::switches::kGuestSession) && |
| user_manager::UserManager::IsInitialized() && |
| user_manager::UserManager::Get()->IsUserLoggedIn(); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| } // namespace |
| |
| ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo( |
| base::Value::Dict manifest_param, |
| const base::FilePath& directory) |
| : manifest(std::move(manifest_param)), root_directory(directory) { |
| if (!root_directory.IsAbsolute()) { |
| CHECK(base::PathService::Get(chrome::DIR_RESOURCES, &root_directory)); |
| root_directory = root_directory.Append(directory); |
| } |
| extension_id = GenerateId(manifest, root_directory); |
| } |
| |
| ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo( |
| ComponentExtensionInfo&& other) |
| : manifest(std::move(other.manifest)), |
| root_directory(std::move(other.root_directory)), |
| extension_id(std::move(other.extension_id)) {} |
| |
| ComponentLoader::ComponentExtensionInfo& |
| ComponentLoader::ComponentExtensionInfo::operator=( |
| ComponentExtensionInfo&& other) { |
| manifest = std::move(other.manifest); |
| root_directory = std::move(other.root_directory); |
| extension_id = std::move(other.extension_id); |
| return *this; |
| } |
| |
| ComponentLoader::ComponentExtensionInfo::~ComponentExtensionInfo() = default; |
| |
| ComponentLoader::ComponentLoader(ExtensionSystem* extension_system, |
| Profile* profile) |
| : profile_(profile), |
| extension_system_(extension_system), |
| ignore_allowlist_for_testing_(false) {} |
| |
| ComponentLoader::~ComponentLoader() = default; |
| |
| void ComponentLoader::LoadAll() { |
| TRACE_EVENT0("browser,startup", "ComponentLoader::LoadAll"); |
| bool is_user_profile = |
| profile_util::ProfileCanUseNonComponentExtensions(profile_); |
| const base::TimeTicks load_start_time = base::TimeTicks::Now(); |
| |
| for (const auto& component_extension : component_extensions_) { |
| Load(component_extension); |
| } |
| |
| const base::TimeDelta load_all_component_time = |
| base::TimeTicks::Now() - load_start_time; |
| UMA_HISTOGRAM_TIMES("Extensions.LoadAllComponentTime", |
| load_all_component_time); |
| if (is_user_profile) { |
| UMA_HISTOGRAM_TIMES("Extensions.LoadAllComponentTime.User", |
| load_all_component_time); |
| } else { |
| UMA_HISTOGRAM_TIMES("Extensions.LoadAllComponentTime.NonUser", |
| load_all_component_time); |
| } |
| } |
| |
| std::optional<base::Value::Dict> ComponentLoader::ParseManifest( |
| base::StringPiece manifest_contents) const { |
| JSONStringValueDeserializer deserializer(manifest_contents); |
| std::unique_ptr<base::Value> manifest = |
| deserializer.Deserialize(nullptr, nullptr); |
| |
| if (!manifest.get() || !manifest->is_dict()) { |
| LOG(ERROR) << "Failed to parse extension manifest."; |
| return std::nullopt; |
| } |
| |
| return std::move(*manifest).TakeDict(); |
| } |
| |
| ExtensionId ComponentLoader::Add(int manifest_resource_id, |
| const base::FilePath& root_directory) { |
| if (!ignore_allowlist_for_testing_ && |
| !IsComponentExtensionAllowlisted(manifest_resource_id)) { |
| return std::string(); |
| } |
| |
| base::StringPiece manifest_contents = |
| ui::ResourceBundle::GetSharedInstance().GetRawDataResource( |
| manifest_resource_id); |
| return Add(manifest_contents, root_directory, true); |
| } |
| |
| ExtensionId ComponentLoader::Add(base::Value::Dict manifest, |
| const base::FilePath& root_directory) { |
| return Add(std::move(manifest), root_directory, false); |
| } |
| |
| ExtensionId ComponentLoader::Add(const base::StringPiece& manifest_contents, |
| const base::FilePath& root_directory) { |
| return Add(manifest_contents, root_directory, false); |
| } |
| |
| ExtensionId ComponentLoader::Add(const base::StringPiece& manifest_contents, |
| const base::FilePath& root_directory, |
| bool skip_allowlist) { |
| // The Value is kept for the lifetime of the ComponentLoader. This is |
| // required in case LoadAll() is called again. |
| std::optional<base::Value::Dict> manifest = ParseManifest(manifest_contents); |
| if (manifest) { |
| return Add(std::move(*manifest), root_directory, skip_allowlist); |
| } |
| return std::string(); |
| } |
| |
| ExtensionId ComponentLoader::Add(base::Value::Dict parsed_manifest, |
| const base::FilePath& root_directory, |
| bool skip_allowlist) { |
| ComponentExtensionInfo info(std::move(parsed_manifest), root_directory); |
| if (!ignore_allowlist_for_testing_ && !skip_allowlist && |
| !IsComponentExtensionAllowlisted(info.extension_id)) { |
| return std::string(); |
| } |
| |
| component_extensions_.push_back(std::move(info)); |
| ComponentExtensionInfo& added_info = component_extensions_.back(); |
| if (extension_system_->is_ready()) { |
| Load(added_info); |
| } |
| return added_info.extension_id; |
| } |
| |
| ExtensionId ComponentLoader::AddOrReplace(const base::FilePath& path) { |
| base::FilePath absolute_path = base::MakeAbsoluteFilePath(path); |
| std::string error; |
| std::optional<base::Value::Dict> manifest( |
| file_util::LoadManifest(absolute_path, &error)); |
| if (!manifest) { |
| LOG(ERROR) << "Could not load extension from '" << absolute_path.value() |
| << "'. " << error; |
| return std::string(); |
| } |
| Remove(GenerateId(*manifest, absolute_path)); |
| |
| // We don't check component extensions loaded by path because this is only |
| // used by developers for testing. |
| return Add(std::move(*manifest), absolute_path, true); |
| } |
| |
| void ComponentLoader::Reload(const ExtensionId& extension_id) { |
| for (const auto& component_extension : component_extensions_) { |
| if (component_extension.extension_id == extension_id) { |
| Load(component_extension); |
| break; |
| } |
| } |
| } |
| |
| void ComponentLoader::Load(const ComponentExtensionInfo& info) { |
| std::string error; |
| scoped_refptr<const Extension> extension(CreateExtension(info, &error)); |
| if (!extension.get()) { |
| LOG(ERROR) << error; |
| return; |
| } |
| |
| CHECK_EQ(info.extension_id, extension->id()) << extension->name(); |
| extension_system_->extension_service()->AddComponentExtension( |
| extension.get()); |
| } |
| |
| void ComponentLoader::Remove(const base::FilePath& root_directory) { |
| // Find the ComponentExtensionInfo for the extension. |
| for (const auto& component_extension : component_extensions_) { |
| if (component_extension.root_directory == root_directory) { |
| Remove(GenerateId(component_extension.manifest, root_directory)); |
| break; |
| } |
| } |
| } |
| |
| void ComponentLoader::Remove(const ExtensionId& id) { |
| for (auto it = component_extensions_.begin(); |
| it != component_extensions_.end(); ++it) { |
| if (it->extension_id == id) { |
| UnloadComponent(&(*it)); |
| component_extensions_.erase(it); |
| break; |
| } |
| } |
| } |
| |
| bool ComponentLoader::Exists(const ExtensionId& id) const { |
| for (const auto& component_extension : component_extensions_) { |
| if (component_extension.extension_id == id) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| std::vector<ExtensionId> ComponentLoader::GetRegisteredComponentExtensionsIds() |
| const { |
| std::vector<ExtensionId> result; |
| for (const auto& el : component_extensions_) { |
| result.push_back(el.extension_id); |
| } |
| return result; |
| } |
| |
| #if BUILDFLAG(ENABLE_HANGOUT_SERVICES_EXTENSION) |
| void ComponentLoader::AddHangoutServicesExtension() { |
| Add(IDR_HANGOUT_SERVICES_MANIFEST, |
| base::FilePath(FILE_PATH_LITERAL("hangout_services"))); |
| } |
| #endif // BUILDFLAG(ENABLE_HANGOUT_SERVICES_EXTENSION) |
| |
| void ComponentLoader::AddNetworkSpeechSynthesisExtension() { |
| Add(IDR_NETWORK_SPEECH_SYNTHESIS_MANIFEST, |
| base::FilePath(FILE_PATH_LITERAL("network_speech_synthesis"))); |
| } |
| |
| void ComponentLoader::AddWithNameAndDescription( |
| int manifest_resource_id, |
| const base::FilePath& root_directory, |
| const std::string& name_string, |
| const std::string& description_string) { |
| if (!ignore_allowlist_for_testing_ && |
| !IsComponentExtensionAllowlisted(manifest_resource_id)) { |
| return; |
| } |
| |
| base::StringPiece manifest_contents = |
| ui::ResourceBundle::GetSharedInstance().GetRawDataResource( |
| manifest_resource_id); |
| |
| // The Value is kept for the lifetime of the ComponentLoader. This is |
| // required in case LoadAll() is called again. |
| std::optional<base::Value::Dict> manifest = ParseManifest(manifest_contents); |
| |
| if (manifest) { |
| manifest->Set(manifest_keys::kName, name_string); |
| manifest->Set(manifest_keys::kDescription, description_string); |
| Add(std::move(*manifest), root_directory, true); |
| } |
| } |
| |
| void ComponentLoader::AddWebStoreApp() { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (!IsNormalSession()) { |
| return; |
| } |
| #endif |
| |
| AddWithNameAndDescription( |
| IDR_WEBSTORE_MANIFEST, base::FilePath(FILE_PATH_LITERAL("web_store")), |
| l10n_util::GetStringUTF8(IDS_WEBSTORE_NAME_STORE), |
| l10n_util::GetStringUTF8(IDS_WEBSTORE_APP_DESCRIPTION)); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| void ComponentLoader::AddChromeApp() { |
| AddWithNameAndDescription( |
| IDR_CHROME_APP_MANIFEST, base::FilePath(FILE_PATH_LITERAL("chrome_app")), |
| crosapi::browser_util::IsAshWebBrowserEnabled() |
| ? l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME) |
| : "Ash Chrome", // Because this is debug only, we do not need i18n. |
| l10n_util::GetStringUTF8(IDS_CHROME_SHORTCUT_DESCRIPTION)); |
| } |
| |
| void ComponentLoader::AddImageLoaderExtension() { |
| Add(IDR_IMAGE_LOADER_MANIFEST, |
| base::FilePath(FILE_PATH_LITERAL("image_loader"))); |
| } |
| |
| void ComponentLoader::AddGuestModeTestExtension(const base::FilePath& path) { |
| base::SysInfo::CrashIfChromeOSNonTestImage(); |
| AddComponentFromDirWithManifestFilename( |
| path, extension_misc::kGuestModeTestExtensionId, |
| extensions::kManifestFilename, extensions::kManifestFilename, |
| base::RepeatingClosure()); |
| } |
| |
| void ComponentLoader::AddKeyboardApp() { |
| Add(IDR_KEYBOARD_MANIFEST, base::FilePath(FILE_PATH_LITERAL("keyboard"))); |
| } |
| |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| scoped_refptr<const Extension> ComponentLoader::CreateExtension( |
| const ComponentExtensionInfo& info, |
| std::string* utf8_error) { |
| // TODO(abarth): We should REQUIRE_MODERN_MANIFEST_VERSION once we've updated |
| // our component extensions to the new manifest version. |
| int flags = Extension::REQUIRE_KEY; |
| return Extension::Create(info.root_directory, |
| mojom::ManifestLocation::kComponent, info.manifest, |
| flags, utf8_error); |
| } |
| |
| // static |
| void ComponentLoader::EnableBackgroundExtensionsForTesting() { |
| g_enable_background_extensions_during_testing = true; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| // static |
| void ComponentLoader::DisableHelpAppForTesting() { |
| g_enable_help_app = false; |
| } |
| #endif |
| |
| void ComponentLoader::AddDefaultComponentExtensions( |
| bool skip_session_components) { |
| // Do not add component extensions that have background pages here -- add them |
| // to AddDefaultComponentExtensionsWithBackgroundPages. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| if (g_enable_help_app) { |
| Add(IDR_HELP_MANIFEST, base::FilePath(FILE_PATH_LITERAL( |
| "/usr/share/chromeos-assets/helpapp"))); |
| } |
| #endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| |
| AddKeyboardApp(); |
| #else // BUILDFLAG(IS_CHROMEOS_ASH) |
| DCHECK(!skip_session_components); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| if (!skip_session_components) { |
| AddWebStoreApp(); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (crosapi::browser_util::IsAshWebBrowserEnabled() || |
| ash::switches::IsAshDebugBrowserEnabled()) { |
| AddChromeApp(); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| #if BUILDFLAG(ENABLE_PDF) |
| Add(pdf_extension_util::GetManifest(), |
| base::FilePath(FILE_PATH_LITERAL("pdf"))); |
| #endif // BUILDFLAG(ENABLE_PDF) |
| } |
| |
| AddDefaultComponentExtensionsWithBackgroundPages(skip_session_components); |
| } |
| |
| void ComponentLoader::AddDefaultComponentExtensionsForKioskMode( |
| bool skip_session_components) { |
| // Do not add component extensions that have background pages here -- add them |
| // to AddDefaultComponentExtensionsWithBackgroundPagesForKioskMode. |
| |
| // No component extension for kiosk app launch splash screen. |
| if (skip_session_components) { |
| return; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // Add virtual keyboard. |
| AddKeyboardApp(); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| AddDefaultComponentExtensionsWithBackgroundPagesForKioskMode(); |
| |
| #if BUILDFLAG(ENABLE_PDF) |
| Add(pdf_extension_util::GetManifest(), |
| base::FilePath(FILE_PATH_LITERAL("pdf"))); |
| #endif |
| } |
| |
| void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages( |
| bool skip_session_components) { |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| |
| // Component extensions with background pages are not enabled during tests |
| // because they generate a lot of background behavior that can interfere. |
| const bool should_disable_background_extensions = |
| !g_enable_background_extensions_during_testing && |
| (command_line->HasSwitch(::switches::kTestType) || |
| command_line->HasSwitch( |
| ::switches::kDisableComponentExtensionsWithBackgroundPages)); |
| |
| #if BUILDFLAG(ENABLE_HANGOUT_SERVICES_EXTENSION) |
| const bool enable_hangout_services_extension_for_testing = |
| command_line->HasSwitch(::switches::kTestType) && |
| command_line->HasSwitch( |
| ::switches::kEnableHangoutServicesExtensionForTesting); |
| if (!skip_session_components && |
| (!should_disable_background_extensions || |
| enable_hangout_services_extension_for_testing)) { |
| AddHangoutServicesExtension(); |
| } |
| #endif // BUILDFLAG(ENABLE_HANGOUT_SERVICES_EXTENSION) |
| |
| if (should_disable_background_extensions) { |
| return; |
| } |
| |
| if (!skip_session_components) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| Add(IDR_ECHO_MANIFEST, |
| base::FilePath(FILE_PATH_LITERAL("/usr/share/chromeos-assets/echo"))); |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| if (!base::FeatureList::IsEnabled( |
| chromeos::features::kDisableOfficeEditingComponentApp)) { |
| AddComponentFromDirWithManifestFilename( |
| base::FilePath("/usr/share/chromeos-assets/quickoffice"), |
| extension_misc::kQuickOfficeComponentExtensionId, |
| extensions::kManifestFilename, extensions::kManifestFilename, |
| base::DoNothing()); |
| } |
| #endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (command_line->HasSwitch(switches::kLoadGuestModeTestExtension)) { |
| base::FilePath path = base::FilePath(command_line->GetSwitchValueASCII( |
| switches::kLoadGuestModeTestExtension)); |
| AddGuestModeTestExtension(path); |
| } |
| AddImageLoaderExtension(); |
| |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| // TODO(https://crbug.com/1005083): Force the off the record profile to be |
| // created to allow the virtual keyboard to work in guest mode. |
| if (!IsNormalSession()) { |
| ExtensionsBrowserClient::Get()->GetOffTheRecordContext(profile_); |
| } |
| #endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| |
| Add(IDR_ARC_SUPPORT_MANIFEST, |
| base::FilePath(FILE_PATH_LITERAL("chromeos/arc_support"))); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) // http://crbug.com/314799 |
| AddNetworkSpeechSynthesisExtension(); |
| #endif |
| |
| #endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| } |
| |
| void ComponentLoader:: |
| AddDefaultComponentExtensionsWithBackgroundPagesForKioskMode() { |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| |
| // Component extensions with background pages are not enabled during tests |
| // because they generate a lot of background behavior that can interfere. |
| if (!g_enable_background_extensions_during_testing && |
| (command_line->HasSwitch(::switches::kTestType) || |
| command_line->HasSwitch( |
| ::switches::kDisableComponentExtensionsWithBackgroundPages))) { |
| return; |
| } |
| |
| #if BUILDFLAG(ENABLE_HANGOUT_SERVICES_EXTENSION) |
| AddHangoutServicesExtension(); |
| #endif // BUILDFLAG(ENABLE_HANGOUT_SERVICES_EXTENSION) |
| } |
| |
| void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) { |
| if (extension_system_->is_ready()) { |
| extension_system_->extension_service()->RemoveComponentExtension( |
| component->extension_id); |
| } |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void ComponentLoader::AddComponentFromDirWithManifestFilename( |
| const base::FilePath& root_directory, |
| const ExtensionId& extension_id, |
| const base::FilePath::CharType* manifest_file_name, |
| const base::FilePath::CharType* guest_manifest_file_name, |
| base::OnceClosure done_cb) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| const base::FilePath::CharType* manifest_filename = |
| IsNormalSession() ? manifest_file_name : guest_manifest_file_name; |
| #else |
| const base::FilePath::CharType* manifest_filename = manifest_file_name; |
| #endif |
| GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&LoadManifestOnFileThread, root_directory, |
| manifest_filename, true), |
| base::BindOnce(&ComponentLoader::FinishAddComponentFromDir, |
| weak_factory_.GetWeakPtr(), root_directory, extension_id, |
| std::nullopt, std::nullopt, std::move(done_cb))); |
| } |
| |
| void ComponentLoader::FinishAddComponentFromDir( |
| const base::FilePath& root_directory, |
| const ExtensionId& extension_id, |
| const std::optional<std::string>& name_string, |
| const std::optional<std::string>& description_string, |
| base::OnceClosure done_cb, |
| std::optional<base::Value::Dict> manifest) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!manifest) { |
| return; // Error already logged. |
| } |
| |
| if (name_string) { |
| manifest->Set(manifest_keys::kName, name_string.value()); |
| } |
| |
| if (description_string) { |
| manifest->Set(manifest_keys::kDescription, description_string.value()); |
| } |
| |
| std::string actual_extension_id = |
| Add(std::move(*manifest), root_directory, false); |
| CHECK_EQ(extension_id, actual_extension_id); |
| if (done_cb) { |
| std::move(done_cb).Run(); |
| } |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| void ComponentLoader::AddComponentFromDir(const base::FilePath& root_directory, |
| const ExtensionId& extension_id, |
| base::OnceClosure done_cb) { |
| AddComponentFromDirWithManifestFilename( |
| root_directory, extension_id, extensions::kManifestFilename, |
| extension_misc::kGuestManifestFilename, std::move(done_cb)); |
| } |
| |
| void ComponentLoader::AddWithNameAndDescriptionFromDir( |
| const base::FilePath& root_directory, |
| const ExtensionId& extension_id, |
| const std::string& name_string, |
| const std::string& description_string) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&LoadManifestOnFileThread, root_directory, |
| extensions::kManifestFilename, false), |
| base::BindOnce(&ComponentLoader::FinishAddComponentFromDir, |
| weak_factory_.GetWeakPtr(), root_directory, extension_id, |
| name_string, description_string, base::OnceClosure())); |
| } |
| |
| void ComponentLoader::AddChromeOsSpeechSynthesisExtensions() { |
| if (!Exists(extension_misc::kGoogleSpeechSynthesisExtensionId)) { |
| AddComponentFromDir( |
| base::FilePath(extension_misc::kGoogleSpeechSynthesisExtensionPath), |
| extension_misc::kGoogleSpeechSynthesisExtensionId, |
| base::BindRepeating( |
| &ComponentLoader::FinishLoadSpeechSynthesisExtension, |
| weak_factory_.GetWeakPtr(), |
| extension_misc::kGoogleSpeechSynthesisExtensionId)); |
| } |
| |
| if (!Exists(extension_misc::kEspeakSpeechSynthesisExtensionId)) { |
| AddComponentFromDir( |
| base::FilePath(extension_misc::kEspeakSpeechSynthesisExtensionPath), |
| extension_misc::kEspeakSpeechSynthesisExtensionId, |
| base::BindRepeating( |
| &ComponentLoader::FinishLoadSpeechSynthesisExtension, |
| weak_factory_.GetWeakPtr(), |
| extension_misc::kEspeakSpeechSynthesisExtensionId)); |
| } |
| } |
| |
| void ComponentLoader::FinishLoadSpeechSynthesisExtension( |
| const ExtensionId& extension_id) { |
| // TODO(https://crbug.com/947305): mitigation for extension not awake after |
| // load. |
| extensions::ProcessManager::Get(profile_)->WakeEventPage(extension_id, |
| base::DoNothing()); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| } // namespace extensions |