blob: d6851009927b88b5516fd5ba3e68049edd379799 [file] [log] [blame]
// Copyright 2024 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/accessibility/embedded_a11y_extension_loader.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/common/extension_l10n_util.h"
#include "extensions/common/file_util.h"
namespace {
std::optional<base::Value::Dict> LoadManifestOnFileThread(
const base::FilePath& path,
const base::FilePath::CharType* manifest_filename,
bool localize) {
CHECK(extensions::GetExtensionFileTaskRunner()->RunsTasksInCurrentSequence());
std::string error;
auto manifest =
extensions::file_util::LoadManifest(path, manifest_filename, &error);
if (!manifest) {
std::ostringstream errorStream;
errorStream << "Can't load "
<< path.Append(manifest_filename).AsUTF8Unsafe() << ": "
<< error;
LOG(ERROR) << errorStream.str();
static auto* const crash_key = base::debug::AllocateCrashKeyString(
"helper_extension_failure", base::debug::CrashKeySize::Size1024);
base::debug::SetCrashKeyString(crash_key, errorStream.str());
base::debug::DumpWithoutCrashing();
return std::nullopt;
}
if (localize) {
// This is only called for Lacros 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(
path, &manifest.value(),
extension_l10n_util::GzippedMessagesPermission::kAllowForTrustedSource,
&error);
CHECK(localized) << error;
}
return manifest;
}
} // namespace
EmbeddedA11yExtensionLoader::ExtensionInfo::ExtensionInfo(
const std::string& extension_id,
const base::FilePath& extension_path,
const base::FilePath::CharType* extension_manifest_file,
bool should_localize)
: extension_id(extension_id),
extension_path(extension_path),
extension_manifest_file(extension_manifest_file),
should_localize(should_localize) {}
EmbeddedA11yExtensionLoader::ExtensionInfo::ExtensionInfo(
const ExtensionInfo& other) = default;
EmbeddedA11yExtensionLoader::ExtensionInfo::ExtensionInfo(ExtensionInfo&&) =
default;
EmbeddedA11yExtensionLoader::ExtensionInfo::~ExtensionInfo() = default;
// static
EmbeddedA11yExtensionLoader* EmbeddedA11yExtensionLoader::GetInstance() {
return base::Singleton<
EmbeddedA11yExtensionLoader,
base::LeakySingletonTraits<EmbeddedA11yExtensionLoader>>::get();
}
EmbeddedA11yExtensionLoader::EmbeddedA11yExtensionLoader() = default;
EmbeddedA11yExtensionLoader::~EmbeddedA11yExtensionLoader() = default;
void EmbeddedA11yExtensionLoader::Init() {
if (initialized_) {
return;
}
ProfileManager* profile_manager = g_browser_process->profile_manager();
profile_manager_observation_.Observe(profile_manager);
// Observe all existing profiles.
std::vector<Profile*> profiles =
g_browser_process->profile_manager()->GetLoadedProfiles();
for (auto* profile : profiles) {
observed_profiles_.AddObservation(profile);
}
for (const auto& extension : extension_map_) {
UpdateAllProfiles(extension.first);
}
initialized_ = true;
}
void EmbeddedA11yExtensionLoader::InstallExtensionWithId(
const std::string& extension_id,
const std::string& extension_resource_directory,
const base::FilePath::CharType* manifest_name,
bool should_localize) {
if (extension_map_.contains(extension_id)) {
return;
}
base::FilePath resources_path;
#if BUILDFLAG(IS_MAC)
base::FilePath root_path;
CHECK(base::PathService::Get(base::DIR_MODULE, &root_path));
resources_path = root_path.Append("resources");
#else
if (!base::PathService::Get(chrome::DIR_RESOURCES, &resources_path)) {
NOTREACHED();
}
#endif
base::FilePath::StringType common_extension_directory;
#if BUILDFLAG(IS_WIN)
common_extension_directory = base::UTF8ToWide(extension_resource_directory);
#else
common_extension_directory = extension_resource_directory;
#endif
auto path = resources_path.Append(common_extension_directory);
ExtensionInfo new_extension = {extension_id, path, manifest_name,
should_localize};
extension_map_.insert({extension_id, new_extension});
UpdateAllProfiles(extension_id);
}
void EmbeddedA11yExtensionLoader::InstallExtensionWithIdAndPath(
const std::string& extension_id,
const base::FilePath& extension_path,
const base::FilePath::CharType* manifest_name,
bool should_localize) {
if (extension_map_.contains(extension_id)) {
return;
}
ExtensionInfo new_extension = {extension_id, extension_path, manifest_name,
should_localize};
extension_map_.insert({extension_id, new_extension});
UpdateAllProfiles(extension_id);
}
void EmbeddedA11yExtensionLoader::RemoveExtensionWithId(
const std::string& extension_id) {
if (!extension_map_.contains(extension_id)) {
return;
}
extension_map_.erase(extension_id);
UpdateAllProfiles(extension_id);
}
void EmbeddedA11yExtensionLoader::AddExtensionChangedCallbackForTest(
base::RepeatingClosure callback) {
extension_installation_changed_callback_for_test_ = std::move(callback);
}
void EmbeddedA11yExtensionLoader::OnProfileWillBeDestroyed(Profile* profile) {
observed_profiles_.RemoveObservation(profile);
}
void EmbeddedA11yExtensionLoader::OnOffTheRecordProfileCreated(
Profile* off_the_record) {
observed_profiles_.AddObservation(off_the_record);
for (const auto& extension : extension_map_) {
UpdateProfile(off_the_record, extension.first);
}
}
void EmbeddedA11yExtensionLoader::OnProfileAdded(Profile* profile) {
observed_profiles_.AddObservation(profile);
for (const auto& extension : extension_map_) {
UpdateProfile(profile, extension.first);
}
}
void EmbeddedA11yExtensionLoader::OnProfileManagerDestroying() {
profile_manager_observation_.Reset();
}
void EmbeddedA11yExtensionLoader::UpdateAllProfiles(
const std::string& extension_id) {
std::vector<Profile*> profiles =
g_browser_process->profile_manager()->GetLoadedProfiles();
for (auto* profile : profiles) {
UpdateProfile(profile, extension_id);
if (profile->HasAnyOffTheRecordProfile()) {
const auto& otr_profiles = profile->GetAllOffTheRecordProfiles();
for (auto* otr_profile : otr_profiles) {
UpdateProfile(otr_profile, extension_id);
}
}
}
}
void EmbeddedA11yExtensionLoader::UpdateProfile(
Profile* profile,
const std::string& extension_id) {
if (extension_map_.contains(extension_id)) {
ExtensionInfo& extension = extension_map_.at(extension_id);
MaybeInstallExtension(profile, extension_id, extension.extension_path,
extension.extension_manifest_file,
extension.should_localize);
} else {
MaybeRemoveExtension(profile, extension_id);
}
}
void EmbeddedA11yExtensionLoader::MaybeRemoveExtension(
Profile* profile,
const std::string& extension_id) {
auto* component_loader = extensions::ComponentLoader::Get(profile);
if (!component_loader || !component_loader->Exists(extension_id)) {
return;
}
component_loader->Remove(extension_id);
if (extension_installation_changed_callback_for_test_) {
extension_installation_changed_callback_for_test_.Run();
}
}
void EmbeddedA11yExtensionLoader::MaybeInstallExtension(
Profile* profile,
const std::string& extension_id,
const base::FilePath& extension_path,
const base::FilePath::CharType* manifest_name,
bool should_localize) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto* component_loader = extensions::ComponentLoader::Get(profile);
if (!component_loader || component_loader->Exists(extension_id)) {
return;
}
extensions::GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&LoadManifestOnFileThread, extension_path, manifest_name,
/*localize=*/should_localize),
base::BindOnce(&EmbeddedA11yExtensionLoader::InstallExtension,
weak_ptr_factory_.GetWeakPtr(), component_loader,
extension_path, extension_id));
}
void EmbeddedA11yExtensionLoader::InstallExtension(
extensions::ComponentLoader* component_loader,
const base::FilePath& path,
const std::string& extension_id,
std::optional<base::Value::Dict> manifest) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (component_loader->Exists(extension_id)) {
// Because this is async and called from another thread, it's possible we
// already installed the extension. Don't try and reinstall in that case.
// This may happen on init, for example, when ash a11y feature state and
// new profiles are loaded all at the same time.
return;
}
// TODO(b/324143642): Extension manifest file should not be null.
// Temporarily logging the error to prevent crashes while we diagnose why it's
// null.
if (!manifest) {
LOG(ERROR) << "Unable to load extension manifest for extension "
<< extension_id << "; Path: " << path;
return;
}
CHECK(manifest) << "Unable to load extension manifest for extension "
<< extension_id << "; Path: " << path;
std::string actual_id =
component_loader->Add(std::move(manifest.value()), path);
CHECK_EQ(actual_id, extension_id);
if (extension_installation_changed_callback_for_test_) {
extension_installation_changed_callback_for_test_.Run();
}
}
bool EmbeddedA11yExtensionLoader::IsExtensionInstalled(
const std::string& extension_id) {
return extension_map_.contains(extension_id);
}