910e62b5创建于 1月15日历史提交
// 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);
}