910e62b5创建于 1月15日历史提交
// Copyright 2023 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/api/user_scripts/user_scripts_api.h"

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/strings/escape.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/optional_util.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_user_script_loader.h"
#include "extensions/browser/scripting_constants.h"
#include "extensions/browser/scripting_utils.h"
#include "extensions/browser/user_script_manager.h"
#include "extensions/browser/user_script_world_configuration_manager.h"
#include "extensions/common/api/extension_types.h"
#include "extensions/common/api/scripts_internal/script_serialization.h"
#include "extensions/common/api/user_scripts.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/mojom/code_injection.mojom-forward.h"
#include "extensions/common/mojom/execution_world.mojom-shared.h"
#include "extensions/common/user_script.h"
#include "extensions/common/utils/content_script_utils.h"

namespace extensions {

namespace {

constexpr char kEmptySourceError[] =
    "User script with ID '*' must specify at least one js source.";
constexpr char kEmptySourceErrorWithoutIdError[] =
    "User script must specify at least one js source.";
constexpr char kInvalidSourceError[] =
    "User script with ID '*' must specify exactly one of 'code' or 'file' as a "
    "js source.";
constexpr char kInvalidSourceWithoutIdError[] =
    "User script must specify exactly one of 'code' or 'file' as a js source.";
constexpr char kMatchesMissingError[] =
    "User script with ID '*' must specify 'matches'.";

// Sanitizes the given `world_id`, updating it if necessary.
// Returns true on success; on failure, returns false and populates `error_out`.
bool IsValidWorldId(api::user_scripts::ExecutionWorld world,
                    std::optional<std::string>& world_id,
                    std::string* error_out) {
  if (!world_id) {
    // Omitting world ID is valid.
    return true;
  }

  if (world != api::user_scripts::ExecutionWorld::kNone &&
      world != api::user_scripts::ExecutionWorld::kUserScript) {
    *error_out = "World ID can only be specified for USER_SCRIPT worlds.";
    return false;
  }

  if (world_id->empty()) {
    // Specifying an empty-string world ID is valid, and will use the default
    // user script world. This is represented by nullopt elsewhere, so we update
    // the world ID value.
    world_id = std::nullopt;
    return true;
  }

  if (world_id->at(0) == '_') {
    *error_out = "World IDs beginning with '_' are reserved.";
    return false;
  }

  static constexpr size_t kMaxWorldIdLength = 256;
  if (world_id->length() > kMaxWorldIdLength) {
    *error_out = "World IDs must be at most 256 characters.";
    return false;
  }

  // Valid world ID!
  return true;
}

mojom::ExecutionWorld ConvertExecutionWorld(
    api::user_scripts::ExecutionWorld world) {
  switch (world) {
    // Execution world defaults to `kUserScript` when it's not provided.
    case api::user_scripts::ExecutionWorld::kNone:
    case api::user_scripts::ExecutionWorld::kUserScript:
      return mojom::ExecutionWorld::kUserScript;
    case api::user_scripts::ExecutionWorld::kMain:
      return mojom::ExecutionWorld::kMain;
  }
}

scripting::InjectionTarget ConvertToInternalInjectionTarget(
    api::user_scripts::InjectionTarget injection_target) {
  scripting::InjectionTarget internal_injection_target;
  internal_injection_target.all_frames = injection_target.all_frames;
  internal_injection_target.document_ids =
      std::move(injection_target.document_ids);
  internal_injection_target.frame_ids = std::move(injection_target.frame_ids);
  internal_injection_target.tab_id = std::move(injection_target.tab_id);
  return internal_injection_target;
}

api::scripts_internal::SerializedUserScript
ConvertRegisteredUserScriptToSerializedUserScript(
    api::user_scripts::RegisteredUserScript user_script) {
  auto user_script_sources_to_serialized_sources =
      [](std::vector<api::user_scripts::ScriptSource> sources) {
        std::vector<api::scripts_internal::ScriptSource> result;
        result.reserve(sources.size());
        for (auto& source : sources) {
          api::scripts_internal::ScriptSource converted_source;
          converted_source.code = std::move(source.code);
          converted_source.file = std::move(source.file);
          result.push_back(std::move(converted_source));
        }
        return result;
      };

  auto convert_execution_world = [](api::user_scripts::ExecutionWorld world) {
    switch (world) {
      // Execution world defaults to `kUserScript` when it's not provided.
      case api::user_scripts::ExecutionWorld::kNone:
      case api::user_scripts::ExecutionWorld::kUserScript:
        return api::extension_types::ExecutionWorld::kUserScript;
      case api::user_scripts::ExecutionWorld::kMain:
        return api::extension_types::ExecutionWorld::kMain;
    }
  };

  api::scripts_internal::SerializedUserScript serialized_script;
  serialized_script.source = api::scripts_internal::Source::kDynamicUserScript;

  serialized_script.all_frames = user_script.all_frames;
  serialized_script.exclude_matches = std::move(user_script.exclude_matches);
  // Note: IDs have already been prefixed appropriately.
  serialized_script.id = std::move(user_script.id);
  serialized_script.include_globs = std::move(user_script.include_globs);
  serialized_script.exclude_globs = std::move(user_script.exclude_globs);
  serialized_script.js =
      user_script_sources_to_serialized_sources(std::move(*user_script.js));
  serialized_script.matches = std::move(*user_script.matches);
  serialized_script.run_at = std::move(user_script.run_at);
  serialized_script.world = convert_execution_world(user_script.world);

  if (base::FeatureList::IsEnabled(
          extensions_features::kApiUserScriptsMultipleWorlds)) {
    serialized_script.world_id = std::move(user_script.world_id);
  }

  return serialized_script;
}

std::unique_ptr<UserScript> ParseUserScript(
    const Extension& extension,
    api::user_scripts::RegisteredUserScript user_script,
    bool allowed_in_incognito,
    std::u16string* error) {
  // Custom validation unique to user scripts.
  // `matches` must be specified for newly-registered scripts, despite being
  // an optional argument.
  if (!user_script.matches) {
    *error = ErrorUtils::FormatErrorMessageUTF16(
        kMatchesMissingError,
        UserScript::TrimPrefixFromScriptID(user_script.id));
    return nullptr;
  }

  // `js` must be existent and not empty.
  if (!user_script.js || user_script.js.value().empty()) {
    *error = ErrorUtils::FormatErrorMessageUTF16(
        kEmptySourceError, UserScript::TrimPrefixFromScriptID(user_script.id));
    return nullptr;
  }

  for (const api::user_scripts::ScriptSource& source : *user_script.js) {
    if ((source.code && source.file) || (!source.code && !source.file)) {
      *error = ErrorUtils::FormatErrorMessageUTF16(
          kInvalidSourceError,
          UserScript::TrimPrefixFromScriptID(user_script.id));
      return nullptr;
    }
  }

  std::string utf8_error;
  if (!IsValidWorldId(user_script.world, user_script.world_id, &utf8_error)) {
    *error = base::UTF8ToUTF16(utf8_error);
    return nullptr;
  }

  // After this, we can just convert to our internal type and rely on our
  // typical parsing to a `UserScript`.
  api::scripts_internal::SerializedUserScript serialized_script =
      ConvertRegisteredUserScriptToSerializedUserScript(std::move(user_script));

  return script_serialization::ParseSerializedUserScript(
      serialized_script, extension, allowed_in_incognito, error);
}

// Converts a UserScript object to a api::user_scripts::RegisteredUserScript
// object, used for getScripts.
api::user_scripts::RegisteredUserScript CreateRegisteredUserScriptInfo(
    const UserScript& script) {
  CHECK_EQ(UserScript::Source::kDynamicUserScript, script.GetSource());

  // To convert a `UserScript`, we first go through our script_internal
  // serialization; this allows us to do simple conversions and avoid any
  // complex logic.
  api::scripts_internal::SerializedUserScript serialized_script =
      script_serialization::SerializeUserScript(script);

  auto convert_serialized_script_sources =
      [](std::vector<api::scripts_internal::ScriptSource> sources) {
        std::vector<api::user_scripts::ScriptSource> converted;
        converted.reserve(sources.size());
        for (auto& source : sources) {
          api::user_scripts::ScriptSource converted_source;
          converted_source.code = std::move(source.code);
          converted_source.file = std::move(source.file);
          converted.push_back(std::move(converted_source));
        }
        return converted;
      };

  auto convert_execution_world =
      [](api::extension_types::ExecutionWorld world) {
        switch (world) {
          case api::extension_types::ExecutionWorld::kNone:
            NOTREACHED()
                << "Execution world should always be present in serialization.";
          case api::extension_types::ExecutionWorld::kIsolated:
            NOTREACHED() << "ISOLATED worlds are not supported in this API.";
          case api::extension_types::ExecutionWorld::kUserScript:
            return api::user_scripts::ExecutionWorld::kUserScript;
          case api::extension_types::ExecutionWorld::kMain:
            return api::user_scripts::ExecutionWorld::kMain;
        }
      };

  api::user_scripts::RegisteredUserScript result;
  result.all_frames = serialized_script.all_frames;
  result.exclude_matches = std::move(serialized_script.exclude_matches);
  result.id = std::move(serialized_script.id);
  result.include_globs = std::move(serialized_script.include_globs);
  result.exclude_globs = std::move(serialized_script.exclude_globs);
  if (serialized_script.js) {
    result.js =
        convert_serialized_script_sources(std::move(*serialized_script.js));
  }
  result.matches = std::move(serialized_script.matches);
  result.run_at = serialized_script.run_at;
  result.world = convert_execution_world(serialized_script.world);
  result.world_id = std::move(serialized_script.world_id);

  return result;
}

}  // namespace

ExtensionFunction::ResponseAction UserScriptsRegisterFunction::Run() {
  std::optional<api::user_scripts::Register::Params> params(
      api::user_scripts::Register::Params::Create(args()));
  EXTENSION_FUNCTION_VALIDATE(params);
  EXTENSION_FUNCTION_VALIDATE(extension());

  std::vector<api::user_scripts::RegisteredUserScript>& scripts =
      params->scripts;
  ExtensionUserScriptLoader* loader =
      ExtensionSystem::Get(browser_context())
          ->user_script_manager()
          ->GetUserScriptLoaderForExtension(extension()->id());

  // Create script ids for dynamic user scripts.
  std::string error;
  std::set<std::string> existing_script_ids =
      loader->GetDynamicScriptIDs(UserScript::Source::kDynamicUserScript);
  std::set<std::string> new_script_ids = scripting::CreateDynamicScriptIds(
      scripts, UserScript::Source::kDynamicUserScript, existing_script_ids,
      &error);

  if (!error.empty()) {
    CHECK(new_script_ids.empty());
    return RespondNow(Error(std::move(error)));
  }

  // Parse user scripts.
  UserScriptList parsed_scripts;
  parsed_scripts.reserve(scripts.size());
  std::u16string parse_error;

  bool allowed_in_incognito = scripting::ScriptsShouldBeAllowedInIncognito(
      extension()->id(), browser_context());

  for (auto& script : scripts) {
    std::unique_ptr<UserScript> user_script = ParseUserScript(
        *extension(), std::move(script), allowed_in_incognito, &parse_error);
    if (!user_script) {
      return RespondNow(Error(base::UTF16ToASCII(parse_error)));
    }

    parsed_scripts.push_back(std::move(user_script));
  }
  scripts.clear();  // The contents of `scripts` have been std::move()d.

  // Add new script IDs now in case another call with the same script IDs is
  // made immediately following this one.
  loader->AddPendingDynamicScriptIDs(std::move(new_script_ids));

  GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread,
                     script_parsing::GetSymlinkPolicy(extension()),
                     std::move(parsed_scripts)),
      base::BindOnce(&UserScriptsRegisterFunction::OnUserScriptFilesValidated,
                     this));

  // Balanced in `OnUserScriptFilesValidated()` or `OnUserScriptsRegistered()`.
  AddRef();
  return RespondLater();
}

void UserScriptsRegisterFunction::OnUserScriptFilesValidated(
    scripting::ValidateScriptsResult result) {
  // We cannot proceed if the `browser_context` is not valid as the
  // `ExtensionSystem` will not exist.
  if (!browser_context()) {
    Release();  // Matches the `AddRef()` in `Run()`.
    return;
  }

  // We cannot proceed if the extension is uninstalled or unloaded in the middle
  // of validating its script files.
  ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
  if (!extension() ||
      !registry->enabled_extensions().Contains(extension_id())) {
    // Note: a Respond() is not needed if the system is shutting down or if the
    // extension is no longer enabled.
    Release();  // Matches the `AddRef()` in `Run()`.
    return;
  }

  auto error = std::move(result.second);
  auto scripts = std::move(result.first);

  std::set<std::string> script_ids;
  for (const auto& script : scripts) {
    script_ids.insert(script->id());
  }
  ExtensionUserScriptLoader* loader =
      ExtensionSystem::Get(browser_context())
          ->user_script_manager()
          ->GetUserScriptLoaderForExtension(extension()->id());

  if (error.has_value()) {
    loader->RemovePendingDynamicScriptIDs(std::move(script_ids));
    Respond(Error(std::move(*error)));
    Release();  // Matches the `AddRef()` in `Run()`.
    return;
  }

  // User scripts are always persisted across sessions.
  loader->AddDynamicScripts(
      std::move(scripts), /*persistent_script_ids=*/std::move(script_ids),
      base::BindOnce(&UserScriptsRegisterFunction::OnUserScriptsRegistered,
                     this));
}

void UserScriptsRegisterFunction::OnUserScriptsRegistered(
    const std::optional<std::string>& error) {
  if (error.has_value()) {
    Respond(Error(std::move(*error)));
  } else {
    Respond(NoArguments());
  }
  Release();  // Matches the `AddRef()` in `Run()`.
}

ExtensionFunction::ResponseAction UserScriptsGetScriptsFunction::Run() {
  std::optional<api::user_scripts::GetScripts::Params> params =
      api::user_scripts::GetScripts::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  std::optional<api::user_scripts::UserScriptFilter>& filter = params->filter;
  std::set<std::string> id_filter;
  if (filter && filter->ids) {
    id_filter.insert(std::make_move_iterator(filter->ids->begin()),
                     std::make_move_iterator(filter->ids->end()));
  }

  ExtensionUserScriptLoader* loader =
      ExtensionSystem::Get(browser_context())
          ->user_script_manager()
          ->GetUserScriptLoaderForExtension(extension()->id());
  const UserScriptList& dynamic_scripts = loader->GetLoadedDynamicScripts();

  std::vector<api::user_scripts::RegisteredUserScript> registered_user_scripts;
  for (const std::unique_ptr<UserScript>& script : dynamic_scripts) {
    if (script->GetSource() != UserScript::Source::kDynamicUserScript) {
      continue;
    }

    std::string id_without_prefix = script->GetIDWithoutPrefix();
    if (filter && filter->ids &&
        !base::Contains(id_filter, id_without_prefix)) {
      continue;
    }

    auto user_script = CreateRegisteredUserScriptInfo(*script);
    // Remove the internally used prefix from the `script`'s ID before
    // returning.
    user_script.id = id_without_prefix;
    registered_user_scripts.push_back(std::move(user_script));
  }

  return RespondNow(ArgumentList(
      api::user_scripts::GetScripts::Results::Create(registered_user_scripts)));
}

ExtensionFunction::ResponseAction UserScriptsUnregisterFunction::Run() {
  std::optional<api::user_scripts::Unregister::Params> params(
      api::user_scripts::Unregister::Params::Create(args()));
  EXTENSION_FUNCTION_VALIDATE(params);
  EXTENSION_FUNCTION_VALIDATE(extension());

  std::optional<api::user_scripts::UserScriptFilter>& filter = params->filter;
  std::optional<std::vector<std::string>> ids = std::nullopt;
  if (filter && filter->ids) {
    ids = filter->ids;
  }

  std::string error;
  bool removal_triggered = scripting::RemoveScripts(
      ids, UserScript::Source::kDynamicUserScript, browser_context(),
      extension()->id(),
      base::BindOnce(&UserScriptsUnregisterFunction::OnUserScriptsUnregistered,
                     this),
      &error);

  if (!removal_triggered) {
    CHECK(!error.empty());
    return RespondNow(Error(std::move(error)));
  }

  return RespondLater();
}

void UserScriptsUnregisterFunction::OnUserScriptsUnregistered(
    const std::optional<std::string>& error) {
  if (error.has_value()) {
    Respond(Error(std::move(*error)));
  } else {
    Respond(NoArguments());
  }
}

ExtensionFunction::ResponseAction UserScriptsUpdateFunction::Run() {
  std::optional<api::user_scripts::Update::Params> params(
      api::user_scripts::Update::Params::Create(args()));
  EXTENSION_FUNCTION_VALIDATE(params);
  EXTENSION_FUNCTION_VALIDATE(extension());

  std::vector<api::user_scripts::RegisteredUserScript>& scripts_to_update =
      params->scripts;
  std::string error;

  // Add the prefix for dynamic user scripts onto the IDs of all `scripts`
  // before continuing.
  std::set<std::string> ids_to_update = scripting::CreateDynamicScriptIds(
      scripts_to_update, UserScript::Source::kDynamicUserScript,
      /*existing_script_ids=*/std::set<std::string>(), &error);

  if (!error.empty()) {
    CHECK(ids_to_update.empty());
    return RespondNow(Error(std::move(error)));
  }

  ExtensionUserScriptLoader* loader =
      ExtensionSystem::Get(browser_context())
          ->user_script_manager()
          ->GetUserScriptLoaderForExtension(extension()->id());

  UserScriptList parsed_scripts = scripting::UpdateScripts(
      scripts_to_update, UserScript::Source::kDynamicUserScript, *loader,
      base::BindRepeating(&CreateRegisteredUserScriptInfo),
      base::BindRepeating(&UserScriptsUpdateFunction::ApplyUpdate, this),
      &error);

  if (!error.empty()) {
    CHECK(parsed_scripts.empty());
    return RespondNow(Error(std::move(error)));
  }

  // Add new script IDs now in case another call with the same script IDs is
  // made immediately following this one.
  loader->AddPendingDynamicScriptIDs(std::move(ids_to_update));

  GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread,
                     script_parsing::GetSymlinkPolicy(extension()),
                     std::move(parsed_scripts)),
      base::BindOnce(&UserScriptsUpdateFunction::OnUserScriptFilesValidated,
                     this));

  // Balanced in `OnUserScriptFilesValidated()`.
  AddRef();
  return RespondLater();
}

std::unique_ptr<UserScript> UserScriptsUpdateFunction::ApplyUpdate(
    api::user_scripts::RegisteredUserScript& new_script,
    api::user_scripts::RegisteredUserScript& original_script,
    std::u16string* parse_error) {
  if (new_script.run_at != api::extension_types::RunAt::kNone) {
    original_script.run_at = new_script.run_at;
  }

  if (new_script.all_frames) {
    original_script.all_frames = *new_script.all_frames;
  }

  if (new_script.matches) {
    original_script.matches = std::move(new_script.matches);
  }

  if (new_script.exclude_matches) {
    original_script.exclude_matches = std::move(new_script.exclude_matches);
  }

  if (new_script.js) {
    original_script.js = std::move(new_script.js);
  }

  if (new_script.world != api::user_scripts::ExecutionWorld::kNone) {
    original_script.world = new_script.world;
  }

  if (new_script.world_id) {
    original_script.world_id = std::move(new_script.world_id);
  }

  // Note: for the update application, we disregard allowed_in_incognito.
  // We'll set it on the resulting scripts.
  constexpr bool kAllowedInIncognito = false;

  std::unique_ptr<UserScript> parsed_script =
      ParseUserScript(*extension(), std::move(original_script),
                      kAllowedInIncognito, parse_error);
  return parsed_script;
}

void UserScriptsUpdateFunction::OnUserScriptFilesValidated(
    scripting::ValidateScriptsResult result) {
  // We cannot proceed if the `browser_context` is not valid as the
  // `ExtensionSystem` will not exist.
  if (!browser_context()) {
    Release();  // Matches the `AddRef()` in `Run()`.
    return;
  }

  // We cannot proceed if the extension is uninstalled or unloaded in the middle
  // of validating its script files.
  ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
  if (!extension() ||
      !registry->enabled_extensions().Contains(extension_id())) {
    // Note: a Respond() is not needed if the system is shutting down or if the
    // extension is no longer enabled.
    Release();  // Matches the `AddRef()` in `Run()`.
    return;
  }

  auto error = std::move(result.second);
  auto scripts = std::move(result.first);
  ExtensionUserScriptLoader* loader =
      ExtensionSystem::Get(browser_context())
          ->user_script_manager()
          ->GetUserScriptLoaderForExtension(extension()->id());

  bool allowed_in_incognito = scripting::ScriptsShouldBeAllowedInIncognito(
      extension()->id(), browser_context());

  std::set<std::string> script_ids;
  for (const auto& script : scripts) {
    script_ids.insert(script->id());
    script->set_incognito_enabled(allowed_in_incognito);
  }

  if (error.has_value()) {
    loader->RemovePendingDynamicScriptIDs(script_ids);
    Respond(Error(std::move(*error)));
    Release();  // Matches the `AddRef()` in `Run()`.
    return;
  }

  // User scripts are always persisted across sessions.
  std::set<std::string> persistent_script_ids = script_ids;
  loader->UpdateDynamicScripts(
      std::move(scripts), std::move(script_ids),
      std::move(persistent_script_ids),
      base::BindOnce(&UserScriptsUpdateFunction::OnUserScriptsUpdated, this));
}

void UserScriptsUpdateFunction::OnUserScriptsUpdated(
    const std::optional<std::string>& error) {
  if (error.has_value()) {
    Respond(Error(std::move(*error)));
  } else {
    Respond(NoArguments());
  }
  Release();  // Matches the `AddRef()` in `Run()`.
}

ExtensionFunction::ResponseAction UserScriptsConfigureWorldFunction::Run() {
  std::optional<api::user_scripts::ConfigureWorld::Params> params(
      api::user_scripts::ConfigureWorld::Params::Create(args()));
  EXTENSION_FUNCTION_VALIDATE(params);
  EXTENSION_FUNCTION_VALIDATE(extension());

  std::optional<std::string> world_id;
  if (base::FeatureList::IsEnabled(
          extensions_features::kApiUserScriptsMultipleWorlds)) {
    world_id = std::move(params->properties.world_id);
  }

  std::string error;
  if (!IsValidWorldId(api::user_scripts::ExecutionWorld::kUserScript, world_id,
                      &error)) {
    return RespondNow(Error(std::move(error)));
  }

  UserScriptWorldConfigurationManager* config_manager =
      UserScriptWorldConfigurationManager::Get(browser_context());
  static constexpr size_t kMaxNumberOfRegisteredWorlds = 100;
  if (config_manager->GetAllUserScriptWorlds(extension()->id()).size() >=
      kMaxNumberOfRegisteredWorlds) {
    return RespondNow(
        Error(base::StringPrintf("You may only configure up to %" PRIuS
                                 " individual user script worlds.",
                                 kMaxNumberOfRegisteredWorlds)));
  }

  mojom::UserScriptWorldInfoPtr world_info =
      config_manager->GetUserScriptWorldInfo(extension()->id(), world_id);
  bool changed = false;

  std::optional<std::string> csp = std::move(params->properties.csp);
  if (csp && csp != world_info->csp) {
    changed = true;
    world_info->csp = std::move(csp);
  }

  std::optional<bool> enable_messaging = params->properties.messaging;
  if (enable_messaging && *enable_messaging != world_info->enable_messaging) {
    changed = true;
    world_info->enable_messaging = *enable_messaging;
  }

  if (changed) {
    config_manager->SetUserScriptWorldInfo(*extension(), std::move(world_info));
  }

  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction UserScriptsExecuteFunction::Run() {
  std::optional<api::user_scripts::Execute::Params> params(
      api::user_scripts::Execute::Params::Create(args()));
  EXTENSION_FUNCTION_VALIDATE(params);
  EXTENSION_FUNCTION_VALIDATE(extension());

  injection_ = std::move(params->injection);

  // Validate injection sources.
  if (injection_.js.empty()) {
    return RespondNow(Error(kEmptySourceErrorWithoutIdError));
  }
  for (const api::user_scripts::ScriptSource& source : injection_.js) {
    if ((source.code && source.file) || (!source.code && !source.file)) {
      return RespondNow(Error(kInvalidSourceWithoutIdError));
    }
  }

  // Validate injection world id.
  std::string error;
  if (!IsValidWorldId(injection_.world, injection_.world_id, &error)) {
    return RespondNow(Error(std::move(error)));
  }

  // Validate injection target.
  scripting::InjectionTarget internal_injection_target =
      ConvertToInternalInjectionTarget(std::move(injection_.target));
  ScriptExecutor* script_executor = nullptr;
  ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES;
  std::set<int> frame_ids;
  if (!scripting::CanAccessTarget(
          *extension()->permissions_data(), internal_injection_target,
          browser_context(), include_incognito_information(), &script_executor,
          &frame_scope, &frame_ids, &error)) {
    return RespondNow(Error(std::move(error)));
  }

  // Retrieve injection sources in order. Since file sources need to be loaded,
  // we use a nullopt for placeholder in the source list. After files are
  // successfully loaded, they should be converted to js source too.
  std::vector<std::string> file_sources;
  std::vector<std::optional<mojom::JSSourcePtr>> sources;
  for (api::user_scripts::ScriptSource& source : injection_.js) {
    if (source.file) {
      file_sources.push_back(std::move(*source.file));
      sources.push_back(std::nullopt);
    } else {
      CHECK(source.code);
      sources.push_back(mojom::JSSource::New(std::move(*source.code), GURL()));
    }
  }

  if (!file_sources.empty()) {
    // JS files don't require localization.
    constexpr bool kRequiresLocalization = false;
    scripting::CheckAndLoadFiles(
        std::move(file_sources), script_parsing::ContentScriptType::kJs,
        *extension(), kRequiresLocalization,
        base::BindOnce(&UserScriptsExecuteFunction::DidLoadResources, this,
                       script_executor, frame_scope, std::move(frame_ids),
                       std::move(sources)),
        &error);
  } else {
    Execute(std::move(sources), script_executor, frame_scope, frame_ids,
            &error);
  }

  return RespondLater();
}

void UserScriptsExecuteFunction::DidLoadResources(
    ScriptExecutor* script_executor,
    ScriptExecutor::FrameScope frame_scope,
    std::set<int> frame_ids,
    std::vector<std::optional<mojom::JSSourcePtr>> sources,
    std::vector<scripting::InjectedFileSource> file_sources,
    std::optional<std::string> load_error) {
  if (load_error) {
    Respond(Error(std::move(*load_error)));
    return;
  }

  DCHECK(!file_sources.empty());

  // Convert the file sources to js and add them to sources in their
  // corresponding execution order.
  int file_index = 0;
  for (std::optional<mojom::JSSourcePtr>& source : sources) {
    // Only file sources have a nullopt placeholder value.
    if (!source.has_value()) {
      CHECK_LT(file_index, static_cast<int>(file_sources.size()));
      source = mojom::JSSource::New(
          std::move(*file_sources[file_index].data),
          extension()->GetResourceURL(
              base::EscapePath(file_sources[file_index].file_name)));
      file_index++;
    }
  }

  // Verify all the file sources where properly added to the sources.
  CHECK_EQ(file_index, static_cast<int>(file_sources.size()));

  std::string error;
  Execute(std::move(sources), script_executor, frame_scope, frame_ids, &error);
}

void UserScriptsExecuteFunction::Execute(
    std::vector<std::optional<mojom::JSSourcePtr>> sources,
    ScriptExecutor* script_executor,
    ScriptExecutor::FrameScope frame_scope,
    std::set<int> frame_ids,
    std::string* error) {
  mojom::ExecutionWorld execution_world =
      ConvertExecutionWorld(injection_.world);
  std::optional<std::string> execution_world_id = injection_.world_id;
  bool inject_immediately = injection_.inject_immediately.value_or(false);

  std::vector<mojom::JSSourcePtr> js_sources;
  js_sources.reserve(sources.size());
  for (std::optional<mojom::JSSourcePtr>& source : sources) {
    CHECK(source.has_value());
    js_sources.push_back(std::move(*source));
  }

  scripting::ExecuteScript(
      extension()->id(), std::move(js_sources), execution_world,
      execution_world_id, script_executor, frame_scope, frame_ids,
      inject_immediately, user_gesture(),
      base::BindOnce(&UserScriptsExecuteFunction::OnScriptExecuted, this));
}

void UserScriptsExecuteFunction::OnScriptExecuted(
    std::vector<ScriptExecutor::FrameResult> frame_results) {
  // If only a single frame was included and the injection failed, respond with
  // an error.
  if (frame_results.size() == 1 && !frame_results[0].error.empty()) {
    Respond(Error(std::move(frame_results[0].error)));
    return;
  }

  // Otherwise, respond successfully. We currently just skip over individual
  // frames that failed. In the future, we can bubble up these error messages
  // to the extension.
  std::vector<api::user_scripts::InjectionResult> injection_results;
  for (auto& result : frame_results) {
    if (!result.error.empty()) {
      continue;
    }
    api::user_scripts::InjectionResult injection_result;
    injection_result.result = std::move(result.value);
    injection_result.frame_id = result.frame_id;
    if (result.document_id) {
      injection_result.document_id = result.document_id.ToString();
    }

    // Put the top frame first; otherwise, any order.
    if (result.frame_id == ExtensionApiFrameIdMap::kTopFrameId) {
      injection_results.insert(injection_results.begin(),
                               std::move(injection_result));
    } else {
      injection_results.push_back(std::move(injection_result));
    }
  }

  Respond(ArgumentList(
      api::user_scripts::Execute::Results::Create(injection_results)));
}

ExtensionFunction::ResponseAction
UserScriptsGetWorldConfigurationsFunction::Run() {
  EXTENSION_FUNCTION_VALIDATE(extension());

  std::vector<mojom::UserScriptWorldInfoPtr> world_configurations =
      UserScriptWorldConfigurationManager::Get(browser_context())
          ->GetAllUserScriptWorlds(extension()->id());

  std::vector<api::user_scripts::WorldProperties> result;
  result.reserve(world_configurations.size());
  for (const auto& world : world_configurations) {
    api::user_scripts::WorldProperties converted;
    converted.messaging = world->enable_messaging;
    converted.csp = world->csp;
    converted.world_id = world->world_id;
    result.push_back(std::move(converted));
  }

  return RespondNow(ArgumentList(
      api::user_scripts::GetWorldConfigurations::Results::Create(result)));
}

ExtensionFunction::ResponseAction
UserScriptsResetWorldConfigurationFunction::Run() {
  std::optional<api::user_scripts::ResetWorldConfiguration::Params> params(
      api::user_scripts::ResetWorldConfiguration::Params::Create(args()));
  EXTENSION_FUNCTION_VALIDATE(params);
  EXTENSION_FUNCTION_VALIDATE(extension());

  // In theory, it'd be safe to just pass in `world_id` without validating it
  // because we should never have an invalid world ID in the preferences. But
  // that's a fragile guarantee and may change if e.g. we start using reserved
  // world IDs. Validate to be on the safe side.
  std::string error;
  if (!IsValidWorldId(api::user_scripts::ExecutionWorld::kUserScript,
                      params->world_id, &error)) {
    return RespondNow(Error(std::move(error)));
  }

  UserScriptWorldConfigurationManager::Get(browser_context())
      ->ClearUserScriptWorldInfo(*extension(), params->world_id);

  return RespondNow(NoArguments());
}

}  // namespace extensions