| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/load_and_localize_file.h" |
| |
| #include "base/ranges/algorithm.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "extensions/browser/component_extension_resource_manager.h" |
| #include "extensions/browser/extensions_browser_client.h" |
| #include "extensions/browser/file_reader.h" |
| #include "extensions/browser/l10n_file_util.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_id.h" |
| #include "extensions/common/extension_l10n_util.h" |
| #include "extensions/common/extension_resource.h" |
| #include "extensions/common/manifest.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "extensions/common/message_bundle.h" |
| #include "ui/base/resource/resource_bundle.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| void MaybeLocalizeInBackground( |
| const ExtensionId& extension_id, |
| const base::FilePath& extension_path, |
| const std::string& extension_default_locale, |
| extension_l10n_util::GzippedMessagesPermission gzip_permission, |
| std::string* data) { |
| bool needs_message_substituion = |
| data->find(extensions::MessageBundle::kMessageBegin) != std::string::npos; |
| if (!needs_message_substituion) |
| return; |
| |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| std::unique_ptr<MessageBundle::SubstitutionMap> localization_messages( |
| l10n_file_util::LoadMessageBundleSubstitutionMap( |
| extension_path, extension_id, extension_default_locale, |
| gzip_permission)); |
| |
| std::string error; |
| MessageBundle::ReplaceMessagesWithExternalDictionary(*localization_messages, |
| data, &error); |
| } |
| |
| // A simple wrapper around MaybeLocalizeInBackground() that returns |data| to |
| // serve as an adapter for PostTaskAndReply. |
| std::vector<std::unique_ptr<std::string>> |
| LocalizeComponentResourcesInBackground( |
| std::vector<std::unique_ptr<std::string>> data, |
| const ExtensionId& extension_id, |
| const base::FilePath& extension_path, |
| const std::string& extension_default_locale, |
| extension_l10n_util::GzippedMessagesPermission gzip_permission) { |
| for (auto& resource : data) { |
| MaybeLocalizeInBackground(extension_id, extension_path, |
| extension_default_locale, gzip_permission, |
| resource.get()); |
| } |
| |
| return data; |
| } |
| |
| // A helper function to load component resources. |
| std::vector<std::unique_ptr<std::string>> LoadComponentResources( |
| const ComponentExtensionResourceManager& resource_manager, |
| const std::vector<ExtensionResource>& resources) { |
| std::vector<std::unique_ptr<std::string>> data; |
| data.reserve(resources.size()); |
| for (const auto& resource : resources) { |
| int resource_id = 0; |
| bool is_component_resource = resource_manager.IsComponentExtensionResource( |
| resource.extension_root(), resource.relative_path(), &resource_id); |
| DCHECK(is_component_resource) |
| << "If any resources passed to LoadAndLocalizeResources() " |
| "are component resources, they all must be."; |
| auto resource_data = std::make_unique<std::string>( |
| ui::ResourceBundle::GetSharedInstance().LoadDataResourceString( |
| resource_id)); |
| data.push_back(std::move(resource_data)); |
| } |
| |
| return data; |
| } |
| |
| } // namespace |
| |
| void LoadAndLocalizeResources(const Extension& extension, |
| std::vector<ExtensionResource> resources, |
| bool localize_file, |
| LoadAndLocalizeResourcesCallback callback) { |
| DCHECK(!resources.empty()); |
| DCHECK(base::ranges::all_of(resources, [](const ExtensionResource& resource) { |
| return !resource.extension_root().empty() && |
| !resource.relative_path().empty(); |
| })); |
| |
| std::string extension_default_locale; |
| if (const std::string* temp = |
| extension.manifest()->FindStringPath(manifest_keys::kDefaultLocale)) { |
| extension_default_locale = *temp; |
| } |
| auto gzip_permission = |
| extension_l10n_util::GetGzippedMessagesPermissionForExtension(&extension); |
| |
| // Check whether the resource should be loaded as a component resource (from |
| // the resource bundle) or read from disk. |
| // We assume (and assert) that if any resource is a component extension |
| // resource, they all must be. Read the first resource passed to check if it |
| // is a component resource, and treat them all as such if it is. |
| const ComponentExtensionResourceManager* |
| component_extension_resource_manager = |
| ExtensionsBrowserClient::Get() |
| ->GetComponentExtensionResourceManager(); |
| int unused_resource_id = 0; |
| bool are_component_resources = |
| component_extension_resource_manager && |
| component_extension_resource_manager->IsComponentExtensionResource( |
| resources.front().extension_root(), resources.front().relative_path(), |
| &unused_resource_id); |
| if (are_component_resources) { |
| std::vector<std::unique_ptr<std::string>> data = LoadComponentResources( |
| *component_extension_resource_manager, resources); |
| // Even if no localization is necessary, we post the task asynchronously |
| // so that |callback| is not run re-entrantly. |
| if (!localize_file) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), std::move(data), absl::nullopt)); |
| } else { |
| auto callback_adapter = |
| [](LoadAndLocalizeResourcesCallback callback, |
| std::vector<std::unique_ptr<std::string>> data) { |
| std::move(callback).Run(std::move(data), absl::nullopt); |
| }; |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, |
| base::BindOnce(&LocalizeComponentResourcesInBackground, |
| std::move(data), extension.id(), extension.path(), |
| extension_default_locale, gzip_permission), |
| base::BindOnce(callback_adapter, std::move(callback))); |
| } |
| |
| return; |
| } |
| |
| // Otherwise, it's not a set of component resources, and we need to load them |
| // from disk. |
| |
| FileReader::OptionalFileSequenceTask get_file_and_l10n_callback; |
| if (localize_file) { |
| get_file_and_l10n_callback = base::BindRepeating( |
| &MaybeLocalizeInBackground, extension.id(), extension.path(), |
| extension_default_locale, gzip_permission); |
| } |
| |
| auto file_reader = base::MakeRefCounted<FileReader>( |
| std::move(resources), std::move(get_file_and_l10n_callback), |
| std::move(callback)); |
| file_reader->Start(); |
| } |
| |
| } // namespace extensions |