// Copyright 2014 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/renderer/dispatcher.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/debug/alias.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/span_printf.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/guest_view/buildflags/buildflags.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/common/api/incognito.h"
#include "extensions/common/api/messaging/message.h"
#include "extensions/common/constants.h"
#include "extensions/common/cors_util.h"
#include "extensions/common/crash_keys.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_api.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/features/behavior_feature.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/features/feature_developer_mode_only.h"
#include "extensions/common/features/feature_provider.h"
#include "extensions/common/features/feature_session_type.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/common/manifest_handlers/options_page_info.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/mojom/host_id.mojom.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/switches.h"
#include "extensions/common/user_scripts_allowed_state.h"
#include "extensions/common/utils/extension_utils.h"
#include "extensions/grit/extensions_renderer_generated_resources.h"
#include "extensions/grit/extensions_renderer_resources.h"
#include "extensions/renderer/api/messaging/native_renderer_messaging_service.h"
#include "extensions/renderer/content_watcher.h"
#include "extensions/renderer/dom_activity_logger.h"
#include "extensions/renderer/extension_frame_helper.h"
#include "extensions/renderer/extension_interaction_provider.h"
#include "extensions/renderer/extensions_renderer_api_provider.h"
#include "extensions/renderer/extensions_renderer_client.h"
#include "extensions/renderer/ipc_message_sender.h"
#include "extensions/renderer/isolated_world_manager.h"
#include "extensions/renderer/module_system.h"
#include "extensions/renderer/native_extension_bindings_system.h"
#include "extensions/renderer/renderer_extension_registry.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/script_context_set.h"
#include "extensions/renderer/script_injection_manager.h"
#include "extensions/renderer/service_worker_data.h"
#include "extensions/renderer/shared_l10n_map.h"
#include "extensions/renderer/static_v8_external_one_byte_string_resource.h"
#include "extensions/renderer/trace_util.h"
#include "extensions/renderer/v8_helpers.h"
#include "extensions/renderer/worker_script_context_set.h"
#include "extensions/renderer/worker_thread_dispatcher.h"
#include "extensions/renderer/worker_thread_util.h"
#include "gin/converter.h"
#include "services/network/public/mojom/cors.mojom.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_proxy.h"
#include "third_party/blink/public/web/web_custom_element.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_frame.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_security_policy.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/public/web/web_v8_features.h"
#include "third_party/blink/public/web/web_view.h"
#include "ui/base/resource/resource_bundle.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-function.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-primitive.h"

using blink::WebDocument;
using blink::WebSecurityPolicy;
using blink::WebString;
using blink::WebView;
using content::RenderThread;

namespace extensions {

namespace {

// A feature flag for the crash issue in crbug.com/389971360.
BASE_FEATURE(kSpeculativeFixForServiceWorkerDataInDidStartServiceWorkerContext,
             base::FEATURE_ENABLED_BY_DEFAULT);
// A feature flag for the crash issue in crbug.com/424476776.
BASE_FEATURE(kSpeculativeFixForNoExtensionInDidStartServiceWorkerContext,
             base::FEATURE_ENABLED_BY_DEFAULT);

static const char kOnSuspendEvent[] = "runtime.onSuspend";
static const char kOnSuspendCanceledEvent[] = "runtime.onSuspendCanceled";

// TODO(crbug.com/389971360, crbug.com/424476776) Remove this enum class and
// `service_worker_context_state` once the issue is fixed. This is completely
// for debugging purpose in crbug.com/389971360. If crbug.com/424476776 relies
// on this enum, see if it can be simplified if the fix is proven.
enum class ServiceWorkerContextState {
  kDefault = 0,
  kInitializing = 1,
  kInitialized = 2,
  kScriptUrlIsNotExtensionScheme = 3,
  kNoExtension = 4,
  kExtensionAPIIsNotEnabledForServiceWorkerScript = 5,
  kDestroying = 6,
  kDestroyed = 7,
  kUnloadedExtension = 8,
  kDestroyedWithoutWorkerData = 9,
  kMaxValue = kDestroyedWithoutWorkerData,
};

constinit thread_local ServiceWorkerContextState service_worker_context_state =
    extensions::ServiceWorkerContextState::kDefault;

enum class ExtensionRendererLoadStatus {
  // Extension is neither loaded in the registry nor unloaded.
  kUnknownExtension = 0,
  // Extension is loaded in the registry.
  kExtensionLoaded = 1,
  // Extension was loaded in the registry, but was unloaded.
  kExtensionUnloaded = 2,
  kMaxValue = kExtensionUnloaded,
};

// Returns whether or not extension APIs are allowed for the specified
// `script_url` and `scope`. The script must be specified in the extension's
// manifest background section and the scope must be the root scope of the
// extension.
bool ExtensionAPIEnabledForServiceWorkerScript(const GURL& scope,
                                               const GURL& script_url) {
  if (!script_url.SchemeIs(kExtensionScheme)) {
    return false;
  }

  // This code runs on the service worker thread, so it needs to use the
  // thread-safe refcounted version of GetExtensionOrAppByURL.
  scoped_refptr<const Extension> extension =
      RendererExtensionRegistry::Get()->GetRefCountedExtensionOrAppByURL(
          script_url);

  if (!extension || !BackgroundInfo::IsServiceWorkerBased(extension.get())) {
    return false;
  }

  if (scope != extension->url()) {
    return false;
  }

  return BackgroundInfo::GetBackgroundServiceWorkerScriptURL(extension.get()) ==
         script_url;
}

// Calls a method |method_name| in a module |module_name| belonging to the
// module system from |context|. Intended as a callback target from
// ScriptContextSet::ForEach.
void CallModuleMethod(const std::string& module_name,
                      const std::string& method_name,
                      const base::ListValue* args,
                      ScriptContext* context) {
  v8::HandleScope handle_scope(context->isolate());
  v8::Context::Scope context_scope(context->v8_context());

  std::unique_ptr<content::V8ValueConverter> converter =
      content::V8ValueConverter::Create();

  v8::LocalVector<v8::Value> arguments(context->isolate());
  for (const auto& arg : *args) {
    arguments.push_back(converter->ToV8Value(arg, context->v8_context()));
  }

  context->module_system()->CallModuleMethodSafe(
      module_name, method_name, &arguments);
}

class HandleScopeHelper {
 public:
  HandleScopeHelper(ScriptContext* script_context)
      : handle_scope_(script_context->isolate()),
        context_scope_(script_context->v8_context()) {}

  HandleScopeHelper(const HandleScopeHelper&) = delete;
  HandleScopeHelper& operator=(const HandleScopeHelper&) = delete;

 private:
  v8::HandleScope handle_scope_;
  v8::Context::Scope context_scope_;
};

base::LazyInstance<WorkerScriptContextSet>::DestructorAtExit
    g_worker_script_context_set = LAZY_INSTANCE_INITIALIZER;

// Creates a new extension from the data in the mojom::ExtensionLoadedParams
// object. A context_id needs to be passed because each browser context can have
// different values for default_policy_blocked/allowed_hosts.
// (see extension_util.cc#GetBrowserContextId)
scoped_refptr<Extension> ConvertToExtension(
    mojom::ExtensionLoadedParamsPtr params,
    int context_id,
    std::u16string* error) {
  // We pass in the |id| to the create call because it will save work in the
  // normal case, and because in tests, extensions may not have paths or keys,
  // but it's important to retain the same id.
  scoped_refptr<Extension> extension =
      Extension::Create(params->path, params->location, params->manifest,
                        params->creation_flags, params->id, error);

  if (!extension.get())
    return extension;

  const PermissionsData* permissions_data = extension->permissions_data();
  permissions_data->SetPermissions(
      std::make_unique<const PermissionSet>(
          std::move(params->active_permissions)),
      std::make_unique<const PermissionSet>(
          std::move(params->withheld_permissions)));
  permissions_data->SetContextId(context_id);

  if (params->uses_default_policy_blocked_allowed_hosts) {
    permissions_data->SetUsesDefaultHostRestrictions();
  } else {
    permissions_data->SetPolicyHostRestrictions(params->policy_blocked_hosts,
                                                params->policy_allowed_hosts);
  }

  for (const auto& pair : params->tab_specific_permissions) {
    permissions_data->UpdateTabSpecificPermissions(pair.first, pair.second);
  }

  extension->SetGUID(params->guid);

  return extension;
}

}  // namespace

Dispatcher::PendingServiceWorker::PendingServiceWorker(
    blink::WebServiceWorkerContextProxy* context_proxy)
    : task_runner(base::SingleThreadTaskRunner::GetCurrentDefault()),
      context_proxy(context_proxy) {
  DCHECK(context_proxy);
}

Dispatcher::PendingServiceWorker::~PendingServiceWorker() = default;

// Note that we can't use Blink public APIs in the constructor because Blink
// is not initialized at the point we create Dispatcher.
Dispatcher::Dispatcher(
    std::vector<std::unique_ptr<const ExtensionsRendererAPIProvider>>
        api_providers)
    : api_providers_(std::move(api_providers)),
      content_watcher_(new ContentWatcher()),
      source_map_(&ui::ResourceBundle::GetSharedInstance()),
      v8_schema_registry_(new V8SchemaRegistry),
      activity_logging_enabled_(false),
      receiver_(this),
      dispatcher_(this) {
  bindings_system_ = CreateBindingsSystem(
      this, IPCMessageSender::CreateMainThreadIPCMessageSender());

  script_context_set_ =
      std::make_unique<ScriptContextSet>(&active_extension_ids_);
  user_script_set_manager_ = std::make_unique<UserScriptSetManager>();
  script_injection_manager_ =
      std::make_unique<ScriptInjectionManager>(user_script_set_manager_.get());
  user_script_set_manager_observation_.Observe(user_script_set_manager_.get());
  PopulateSourceMap();
  // Ideally this should be done after checking
  // ExtensionAPIEnabledInExtensionServiceWorkers(), but the Dispatcher is
  // created so early that sending an IPC from browser/ process to synchronize
  // this enabled-ness is too late.
  WorkerThreadDispatcher::Get()->Init(RenderThread::Get());

  // Register WebSecurityPolicy allowlists for the chrome-extension:// scheme.
  WebString extension_scheme(WebString::FromAscii(kExtensionScheme));

  // Extension resources are HTTP-like and safe to expose to the fetch API. The
  // rules for the fetch API are consistent with XHR.
  WebSecurityPolicy::RegisterURLSchemeAsSupportingFetchAPI(extension_scheme);

  // Register WebSecurityPolicy allowlists for the file:// scheme.
  WebString file_scheme(WebString::FromAscii(url::kFileScheme));

  // Extensions are allowed to make cross-origin requests to file scheme iff the
  // user explicitly grants them access post-installation in the
  // chrome://extensions page.
  WebSecurityPolicy::RegisterURLSchemeAsSupportingFetchAPI(file_scheme);

  // Extension resources, when loaded as the top-level document, should bypass
  // Blink's strict first-party origin checks.
  WebSecurityPolicy::RegisterURLSchemeAsFirstPartyWhenTopLevel(
      extension_scheme);

  // Disallow running javascript URLs on the chrome-extension scheme.
  WebSecurityPolicy::RegisterURLSchemeAsNotAllowingJavascriptURLs(
      extension_scheme);

#if !BUILDFLAG(IS_ANDROID)
  // Currently, extensions are only available on desktop, and are process-
  // separated from regular web contents. Therefore, it is safe to give them
  // access to SharedArrayBuffers.
  WebSecurityPolicy::RegisterURLSchemeAsAllowingSharedArrayBuffers(
      extension_scheme);
#endif

  // chrome-extension: resources should be allowed to register ServiceWorkers.
  WebSecurityPolicy::RegisterURLSchemeAsAllowingServiceWorkers(
      extension_scheme);

  WebSecurityPolicy::RegisterURLSchemeAsAllowingWasmEvalCSP(extension_scheme);

  // Initialize host permissions for any extensions that were activated before
  // WebKit was initialized.
  for (const ExtensionId& extension_id : active_extension_ids_) {
    const Extension* extension =
        RendererExtensionRegistry::Get()->GetByID(extension_id);
    CHECK(extension);
    InitOriginPermissions(extension);
  }

  EnableCustomElementAllowlist();
}

Dispatcher::~Dispatcher() {
}

// static
WorkerScriptContextSet* Dispatcher::GetWorkerScriptContextSet() {
  return &(g_worker_script_context_set.Get());
}

// static
bool Dispatcher::ShouldNotifyServiceWorkerOnWebSocketActivity(
    v8::Local<v8::Context> v8_context) {
  ScriptContext* script_context =
      GetWorkerScriptContextSet()->GetContextByV8Context(v8_context);
  // Only notify on web socket activity if the service worker is the background
  // service worker for an extension.
  return script_context &&
         ExtensionAPIEnabledForServiceWorkerScript(
             script_context->service_worker_scope(), script_context->url());
}

void Dispatcher::OnRenderFrameCreated(content::RenderFrame* render_frame) {
  script_injection_manager_->OnRenderFrameCreated(render_frame);
  content_watcher_->OnRenderFrameCreated(render_frame);

  // The RenderFrame comes with the initial empty document already created.
  DidCreateDocumentElement(render_frame->GetWebFrame());
  // We run scripts on the empty document.
  RunScriptsAtDocumentStart(render_frame);
}

bool Dispatcher::IsExtensionActive(const ExtensionId& extension_id) const {
  const bool is_active = active_extension_ids_.contains(extension_id);
  if (is_active)
    CHECK(RendererExtensionRegistry::Get()->Contains(extension_id));
  return is_active;
}

void Dispatcher::DidCreateScriptContext(
    blink::WebLocalFrame* frame,
    const v8::Local<v8::Context>& v8_context,
    int32_t world_id) {
  const base::TimeTicks start_time = base::TimeTicks::Now();

  ScriptContext* context = script_context_set_->Register(
      frame, v8_context, world_id,
      /*is_webview=*/webview_partition_id_.has_value());

  // Initialize origin permissions for content scripts, which can't be
  // initialized in |ActivateExtension|.
  if (context->context_type() == mojom::ContextType::kContentScript) {
    InitOriginPermissions(context->extension());
  }

  context->SetModuleSystem(
      std::make_unique<ModuleSystem>(context, &source_map_));

  ModuleSystem* module_system = context->module_system();

  // Enable natives in startup.
  ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system);

  RegisterNativeHandlers(module_system, context, bindings_system_.get(),
                         v8_schema_registry_.get());

  bindings_system_->DidCreateScriptContext(context);

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
  // Inject custom JS into the platform app context.
  if (IsWithinPlatformApp()) {
    module_system->Require("platformApp");
  }
#endif

#if BUILDFLAG(ENABLE_GUEST_VIEW)
  RequireGuestViewModules(context);
#endif

  const base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
  switch (context->context_type()) {
    case mojom::ContextType::kUnspecified:
      UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Unspecified",
                          elapsed);
      break;
    case mojom::ContextType::kPrivilegedExtension:
      // For service workers this is handled in
      // WillEvaluateServiceWorkerOnWorkerThread().
      DCHECK(!context->IsForServiceWorker());
      UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Blessed", elapsed);
      break;
    case mojom::ContextType::kUnprivilegedExtension:
      UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Unblessed",
                          elapsed);
      break;
    case mojom::ContextType::kContentScript:
      UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_ContentScript",
                          elapsed);
      break;
    case mojom::ContextType::kWebPage:
      UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_WebPage", elapsed);
      break;
    case mojom::ContextType::kPrivilegedWebPage:
      UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_BlessedWebPage",
                          elapsed);
      break;
    case mojom::ContextType::kWebUi:
      UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_WebUI", elapsed);
      break;
    case mojom::ContextType::kUntrustedWebUi:
      // Extension APIs in untrusted WebUIs are temporary so don't bother
      // recording metrics for them.
      break;
    case mojom::ContextType::kOffscreenExtension:
    case mojom::ContextType::kUserScript:
      // We don't really care about offscreen extension context or user script
      // context initialization time at the moment. Offscreen extension context
      // initialization is a strict subset (and very similar to) privileged
      // extension context time, while user script context initialization is
      // very similar to content script initialization.
      break;
  }

  VLOG(1) << "Num tracked contexts: " << script_context_set_->size();

  ExtensionFrameHelper* frame_helper =
      ExtensionFrameHelper::Get(content::RenderFrame::FromWebFrame(frame));
  if (!frame_helper)
    return;  // The frame is invisible to extensions.

  frame_helper->NotifyDidCreateScriptContext(world_id);
}

void Dispatcher::DidInitializeServiceWorkerContextOnWorkerThread(
    blink::WebServiceWorkerContextProxy* context_proxy,
    const GURL& service_worker_scope,
    const GURL& script_url) {
  if (!script_url.SchemeIs(kExtensionScheme))
    return;

  // Defer `PrepareForEvaluation` until `ModuleSystem` is created.
  context_proxy->DeferPrepareForEvaluation();

  {
    base::AutoLock lock(service_workers_paused_for_on_loaded_message_lock_);
    ExtensionId extension_id =
        RendererExtensionRegistry::Get()->GetExtensionOrAppIDByURL(script_url);
    // If the extension is already loaded we don't have to suspend the service
    // worker. The service worker will continue in
    // Dispatcher::WillEvaluateServiceWorkerOnWorkerThread().
    if (RendererExtensionRegistry::Get()->GetByID(extension_id))
      return;

    // Suspend the service worker until loaded message of the extension comes.
    // The service worker will be resumed in Dispatcher::OnLoaded().
    context_proxy->PauseEvaluation();
    service_workers_paused_for_on_loaded_message_.emplace(
        extension_id, std::make_unique<PendingServiceWorker>(context_proxy));
  }
}

void Dispatcher::WillEvaluateServiceWorkerOnWorkerThread(
    blink::WebServiceWorkerContextProxy* context_proxy,
    v8::Local<v8::Context> v8_context,
    int64_t service_worker_version_id,
    const GURL& service_worker_scope,
    const GURL& script_url,
    const blink::ServiceWorkerToken& service_worker_token) {
  service_worker_context_state = ServiceWorkerContextState::kInitializing;

  // TODO(crbug.com/40626913): We may want to give service workers not
  // registered by extensions minimal bindings, the same as other webpage-like
  // contexts.
  if (!script_url.SchemeIs(kExtensionScheme)) {
    // Early-out if this isn't a chrome-extension:// scheme, because looking up
    // the extension registry is unnecessary if it's not. Checking this will
    // also skip over hosted apps, which is the desired behavior - hosted app
    // service workers are not our concern.
    service_worker_context_state =
        ServiceWorkerContextState::kScriptUrlIsNotExtensionScheme;
    return;
  }

  // Runs the deferred `PrepareForEvaluation` before this returns.
  base::ScopedClosureRunner run_at_return(base::BindOnce(
      [](blink::WebServiceWorkerContextProxy* context_proxy) {
        context_proxy->RunDeferredPrepareForEvaluation();
      },
      context_proxy));

  // This function is called from a service worker thread, so we use the
  // thread-safe refcounted version of GetExtensionOrAppByURL.
  scoped_refptr<const Extension> extension =
      RendererExtensionRegistry::Get()->GetRefCountedExtensionOrAppByURL(
          script_url);

  ExtensionRendererLoadStatus load_status =
      ExtensionRendererLoadStatus::kUnknownExtension;
  if (extension) {
    load_status = ExtensionRendererLoadStatus::kExtensionLoaded;
  } else if (unloaded_extensions_.contains(script_url.GetHost())) {
    // script_url.host() is the extension's ID.
    load_status = ExtensionRendererLoadStatus::kExtensionUnloaded;
  }
  base::UmaHistogramEnumeration(
      "Extensions.ServiceWorkerRenderer."
      "ExtensionLoadStatusInWorkerScriptEvaluation",
      load_status);

  if (!extension) {
    // Set the context state based on the extension load status.
    if (load_status == ExtensionRendererLoadStatus::kExtensionUnloaded) {
      service_worker_context_state =
          ServiceWorkerContextState::kUnloadedExtension;
    } else {
      service_worker_context_state = ServiceWorkerContextState::kNoExtension;
    }

    // TODO(kalman): This is no good. Instead we need to either:
    //
    // - Hold onto the v8::Context and create the ScriptContext and install
    //   our bindings when this extension is loaded.
    // - Deal with there being an extension ID (script_url.host()) but no
    //   extension associated with it.
    //
    // The former is safer, but is unfriendly to caching (e.g. session restore).
    // It seems to contradict the service worker idiom.
    //
    // The latter is friendly to caching, but running extension code without an
    // installed extension makes me nervous, and means that we won't be able to
    // expose arbitrary (i.e. capability-checked) extension APIs to service
    // workers. We will probably need to relax some assertions - we just need
    // to find them.
    //
    // Perhaps this could be solved with our own event on the service worker
    // saying that an extension is ready, and documenting that extension APIs
    // won't work before that event has fired?
    return;
  }

  if (!ExtensionAPIEnabledForServiceWorkerScript(service_worker_scope,
                                                 script_url)) {
    service_worker_context_state = ServiceWorkerContextState::
        kExtensionAPIIsNotEnabledForServiceWorkerScript;
    return;
  }

  const int thread_id = content::WorkerThread::GetCurrentId();
  CHECK_NE(thread_id, kMainThreadId);

  // Only the script specific in the manifest's background data gets bindings.
  //
  // TODO(crbug.com/40626913): We may want to give other service workers
  // registered by extensions minimal bindings, just as we might want to give
  // them to service workers that aren't registered by extensions.
  ScriptContext* context = new ScriptContext(
      v8_context, nullptr, GenerateHostIdFromExtensionId(extension->id()),
      extension.get(), /*blink_isolated_world_id=*/std::nullopt,
      mojom::ContextType::kPrivilegedExtension, extension.get(),
      mojom::ContextType::kPrivilegedExtension);
  context->set_url(script_url);
  context->set_service_worker_scope(service_worker_scope);
  context->set_service_worker_version_id(service_worker_version_id);

  WorkerThreadDispatcher* worker_dispatcher = WorkerThreadDispatcher::Get();
  std::optional<base::UnguessableToken> worker_activation_token =
      RendererExtensionRegistry::Get()->GetWorkerActivationToken(
          extension->id());

  std::unique_ptr<IPCMessageSender> ipc_sender =
      IPCMessageSender::CreateWorkerThreadIPCMessageSender(
          worker_dispatcher, context_proxy, service_worker_version_id);

  CHECK(extension);
  CHECK(worker_activation_token.has_value());

  worker_dispatcher->AddWorkerData(
      context_proxy, service_worker_version_id, worker_activation_token,
      service_worker_token, context,
      CreateBindingsSystem(worker_dispatcher, std::move(ipc_sender)));

  // TODO(lazyboy): Make sure accessing |source_map_| in worker thread is
  // safe.
  context->SetModuleSystem(
      std::make_unique<ModuleSystem>(context, &source_map_));

  ModuleSystem* module_system = context->module_system();
  // Enable natives in startup.
  ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system);
  NativeExtensionBindingsSystem* worker_bindings_system =
      WorkerThreadDispatcher::GetBindingsSystem();
  RegisterNativeHandlers(module_system, context, worker_bindings_system,
                         WorkerThreadDispatcher::GetV8SchemaRegistry());

  worker_bindings_system->DidCreateScriptContext(context);

#if BUILDFLAG(ENABLE_GUEST_VIEW)
  // TODO(lazyboy): Get rid of RequireGuestViewModules() as this doesn't seem
  // necessary for Extension SW.
  RequireGuestViewModules(context);
#endif

  WorkerThreadDispatcher::GetServiceWorkerData()->Init();
  g_worker_script_context_set.Get().Insert(base::WrapUnique(context));

  service_worker_context_state = ServiceWorkerContextState::kInitialized;
}

void Dispatcher::WillReleaseScriptContext(
    blink::WebLocalFrame* frame,
    const v8::Local<v8::Context>& v8_context,
    int32_t world_id) {
  ScriptContext* context = script_context_set_->GetByV8Context(v8_context);
  if (!context)
    return;
  bindings_system_->WillReleaseScriptContext(context);

  script_context_set_->Remove(context);
  VLOG(1) << "Num tracked contexts: " << script_context_set_->size();
}

void Dispatcher::DidStartServiceWorkerContextOnWorkerThread(
    int64_t service_worker_version_id,
    const GURL& service_worker_scope,
    const GURL& script_url,
    const blink::ServiceWorkerToken& service_worker_token) {
  if (!ExtensionAPIEnabledForServiceWorkerScript(service_worker_scope,
                                                 script_url)) {
    return;
  }

  // Even if the extension appears in the registry there's a timing issue where
  // an extension that is reloaded could race with this (stale) async task being
  // posted for a previously evaluated worker script.
  // In that case we'd see this start notification for the previous worker
  // instance that was quickly terminated (for example: a JS syntax error on
  // script evaluation, but not limited to that) and we wouldn't have a
  // ServiceWorkerData/ScriptContext to use later in this function.
  // We'll get another DidStartServiceWorkerContextOnWorkerThread() when the
  // next worker instance starts so returning early here shouldn't cause any
  // issues.
  if (base::FeatureList::IsEnabled(
          kSpeculativeFixForNoExtensionInDidStartServiceWorkerContext) &&
      service_worker_context_state ==
          ServiceWorkerContextState::kUnloadedExtension) {
    return;
  }

  // TODO(crbug.com/389971360) Remove this once the bug is fixed.
  SCOPED_CRASH_KEY_NUMBER("extensions", "worker_context_state",
                          static_cast<int>(service_worker_context_state));

  const int thread_id = content::WorkerThread::GetCurrentId();
  CHECK_NE(thread_id, kMainThreadId);
  auto* service_worker_data = WorkerThreadDispatcher::GetServiceWorkerData();
  if (base::FeatureList::IsEnabled(
          kSpeculativeFixForServiceWorkerDataInDidStartServiceWorkerContext)) {
    // `service_worker_data` can be nullptr if the extension is already unloaded
    // and the worker thread termination started.
    //
    // TODO(crbug.com/389971360) If this does seem to fix it, we should check
    // `thread_state_` or `requested_to_terminate_` to confirm we're in
    // termination when `service_worker_data` is false here.
    if (service_worker_data) {
      const ExtensionId& extension_id =
          service_worker_data->context()->GetExtensionID();
      CHECK(!extension_id.empty());
      service_worker_data->GetServiceWorkerHost()->DidStartServiceWorkerContext(
          extension_id, *service_worker_data->activation_sequence(),
          service_worker_scope, service_worker_version_id, thread_id,
          service_worker_token);
    }
  } else {
    CHECK(service_worker_data);
    const ExtensionId& extension_id =
        service_worker_data->context()->GetExtensionID();
    CHECK(!extension_id.empty());
    service_worker_data->GetServiceWorkerHost()->DidStartServiceWorkerContext(
        extension_id, *service_worker_data->activation_sequence(),
        service_worker_scope, service_worker_version_id, thread_id,
        service_worker_token);
  }
}

void Dispatcher::WillDestroyServiceWorkerContextOnWorkerThread(
    v8::Local<v8::Context> v8_context,
    int64_t service_worker_version_id,
    const GURL& service_worker_scope,
    const GURL& script_url,
    const blink::ServiceWorkerToken& service_worker_token) {
  // Note that using ExtensionAPIEnabledForServiceWorkerScript() won't work here
  // as RendererExtensionRegistry might have already unloaded this extension.
  // Use the existence of ServiceWorkerData as the source of truth instead.
  if (auto* service_worker_data =
          WorkerThreadDispatcher::GetServiceWorkerData()) {
    service_worker_context_state = ServiceWorkerContextState::kDestroying;
    const int thread_id = content::WorkerThread::GetCurrentId();
    CHECK_NE(thread_id, kMainThreadId);

    // TODO(lazyboy/devlin): Should this cleanup happen in a worker class, like
    // WorkerThreadDispatcher? If so, we should move the initialization as well.
    ScriptContext* script_context = service_worker_data->context();
    const ExtensionId& extension_id =
        service_worker_data->context()->GetExtensionID();
    CHECK(!extension_id.empty());
    NativeExtensionBindingsSystem* worker_bindings_system =
        service_worker_data->bindings_system();
    if (worker_bindings_system) {
      worker_bindings_system->WillReleaseScriptContext(script_context);
      worker_bindings_system->messaging_service()->InvalidatePorts(
          script_context);
      service_worker_data->GetServiceWorkerHost()->DidStopServiceWorkerContext(
          extension_id, *service_worker_data->activation_sequence(),
          service_worker_scope, service_worker_version_id, thread_id,
          service_worker_token);
    }
    // Note: we have to remove the context (and thus perform invalidation on
    // the native handlers) prior to removing the worker data, which destroys
    // the associated bindings system.
    g_worker_script_context_set.Get().Remove(v8_context, script_url);
    WorkerThreadDispatcher::Get()->RemoveWorkerData(service_worker_version_id);

    // TODO(crbug.com/389971360) Remove this after the fix. If ServiceWorkerData
    // in `WorkerThreadDispatcher` is removed, update the eligibility status so
    // that we make sure if ServiceWorkerData is already removed or not in the
    // crash at `DidStartServiceWorkerContextOnWorkerThread()`.
    service_worker_context_state = ServiceWorkerContextState::kDestroyed;
  } else {
    // If extension APIs in service workers aren't enabled, we just need to
    // remove the context.
    g_worker_script_context_set.Get().Remove(v8_context, script_url);
    // TODO(crbug.com/424476776) Remove this after the fix.
    service_worker_context_state =
        ServiceWorkerContextState::kDestroyedWithoutWorkerData;
  }

  ExtensionId extension_id =
      RendererExtensionRegistry::Get()->GetExtensionOrAppIDByURL(script_url);
  {
    base::AutoLock lock(service_workers_paused_for_on_loaded_message_lock_);
    service_workers_paused_for_on_loaded_message_.erase(extension_id);
  }
}

void Dispatcher::DidCreateDocumentElement(blink::WebLocalFrame* frame) {
  // Note: use GetEffectiveDocumentURLForContext() and not just
  // frame->document()->url() so that this also injects the stylesheet on
  // about:blank frames that are hosted in the extension process. (Even though
  // this is used to determine whether to inject a stylesheet, we don't use
  // GetEffectiveDocumentURLForInjection() because we inject based on whether
  // it is an extension context, rather than based on the extension's injection
  // permissions.)
  GURL effective_document_url =
      ScriptContext::GetEffectiveDocumentURLForContext(
          frame, frame->GetDocument().Url(), true /* match_about_blank */);

  const Extension* extension =
      RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(
          effective_document_url);

  if (extension &&
      (extension->is_extension() || extension->is_platform_app())) {
    int resource_id =
        extension->is_platform_app()
            ? IDR_EXTENSIONS_RENDERER_GENERATED_PLATFORM_APP_CSS
            : IDR_EXTENSIONS_RENDERER_GENERATED_EXTENSION_FONTS_CSS;
    std::string stylesheet =
        ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
            resource_id);
    base::ReplaceFirstSubstringAfterOffset(
        &stylesheet, 0, "$FONTFAMILY", system_font_family_);
    base::ReplaceFirstSubstringAfterOffset(
        &stylesheet, 0, "$FONTSIZE", system_font_size_);

    // Blink doesn't let us define an additional user agent stylesheet, so
    // we insert the default platform app or extension stylesheet into all
    // documents that are loaded in each app or extension.
    frame->GetDocument().InsertStyleSheet(WebString::FromUtf8(stylesheet));
  }

  // If this is an extension options page, and the extension has opted into
  // using Chrome styles, then insert the Chrome extension stylesheet.
  if (extension && extension->is_extension() &&
      OptionsPageInfo::ShouldUseChromeStyle(extension) &&
      effective_document_url == OptionsPageInfo::GetOptionsPage(extension)) {
    std::string extension_css =
        ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
            IDR_EXTENSION_CSS);
    frame->GetDocument().InsertStyleSheet(WebString::FromUtf8(extension_css));
  }
}

void Dispatcher::RunScriptsAtDocumentStart(content::RenderFrame* render_frame) {
  ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
  if (!frame_helper)
    return;  // The frame is invisible to extensions.

  frame_helper->RunScriptsAtDocumentStart();
  // |frame_helper| and |render_frame| might be dead by now.
}

void Dispatcher::RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) {
  ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
  if (!frame_helper)
    return;  // The frame is invisible to extensions.

  frame_helper->RunScriptsAtDocumentEnd();
  // |frame_helper| and |render_frame| might be dead by now.
}

void Dispatcher::RunScriptsAtDocumentIdle(content::RenderFrame* render_frame) {
  ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
  if (!frame_helper)
    return;  // The frame is invisible to extensions.

  frame_helper->RunScriptsAtDocumentIdle();
  // |frame_helper| and |render_frame| might be dead by now.
}

void Dispatcher::DispatchEventHelper(
    const mojom::HostID& host_id,
    const std::string& event_name,
    const base::ListValue& event_args,
    mojom::EventFilteringInfoPtr filtering_info) const {
  script_context_set_->ForEach(
      host_id, nullptr,
      base::BindRepeating(
          &NativeExtensionBindingsSystem::DispatchEventInContext,
          base::Unretained(bindings_system_.get()), event_name,
          std::cref(event_args), base::OwnedRef(std::move(filtering_info))));
  bindings_system_->DidDispatchEvent(host_id, event_name, event_args);
}

void Dispatcher::InvokeModuleSystemMethod(content::RenderFrame* render_frame,
                                          const ExtensionId& extension_id,
                                          const std::string& module_name,
                                          const std::string& function_name,
                                          const base::ListValue& args) {
  script_context_set_->ForEach(
      GenerateHostIdFromExtensionId(extension_id), render_frame,
      base::BindRepeating(&CallModuleMethod, module_name, function_name,
                          &args));
}

void Dispatcher::ExecuteDeclarativeScript(content::RenderFrame* render_frame,
                                          int tab_id,
                                          const ExtensionId& extension_id,
                                          const std::string& script_id,
                                          const GURL& url) {
  TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::ExecuteDeclarativeScript",
                                 extension_id);
  script_injection_manager_->ExecuteDeclarativeScript(
      render_frame, tab_id, extension_id, script_id, url);
}

void Dispatcher::ExecuteCode(mojom::ExecuteCodeParamsPtr param,
                             mojom::LocalFrame::ExecuteCodeCallback callback,
                             content::RenderFrame* render_frame) {
  TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::ExecuteCode", param->host_id->id);
  script_injection_manager_->HandleExecuteCode(
      std::move(param), std::move(callback), render_frame);
}

void Dispatcher::RegisterMojoInterfaces(
    blink::AssociatedInterfaceRegistry* associated_interfaces) {
  // This base::Unretained() is safe, because:
  // 1) the Dispatcher is a RenderThreadObserver and outlives the RenderThread.
  // 2) |asscoiated_interfaces| is owned by the RenderThread.
  // As well the Dispatcher is owned by the
  // ExtensionsRendererClient, which in turn is a leaky LazyInstance (and thus
  // never deleted).
  associated_interfaces->AddInterface<mojom::Renderer>(base::BindRepeating(
      &Dispatcher::OnRendererAssociatedRequest, base::Unretained(this)));
  associated_interfaces->AddInterface<mojom::EventDispatcher>(
      base::BindRepeating(&Dispatcher::OnEventDispatcherRequest,
                          base::Unretained(this)));
}

void Dispatcher::UnregisterMojoInterfaces(
    blink::AssociatedInterfaceRegistry* associated_interfaces) {
  associated_interfaces->RemoveInterface(mojom::Renderer::Name_);
  associated_interfaces->RemoveInterface(mojom::EventDispatcher::Name_);
}

void Dispatcher::OnRendererAssociatedRequest(
    mojo::PendingAssociatedReceiver<mojom::Renderer> receiver) {
  receiver_.Bind(std::move(receiver));
}

void Dispatcher::OnEventDispatcherRequest(
    mojo::PendingAssociatedReceiver<mojom::EventDispatcher> dispatcher) {
  CHECK(!dispatcher_.is_bound());
  dispatcher_.Bind(std::move(dispatcher));
  dispatcher_.reset_on_disconnect();
}

void Dispatcher::ActivateExtension(const ExtensionId& extension_id) {
  TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::ActivateExtension", extension_id);

  CHECK(!extension_id.empty());
  const Extension* extension =
      RendererExtensionRegistry::Get()->GetByID(extension_id);
  if (!extension) {
    // Extension was activated but was never loaded. This probably means that
    // the renderer failed to load it (or the browser failed to tell us when it
    // did). Failures shouldn't happen, but instead of crashing there (which
    // executes on all renderers) just log an error and dump without crashing.
    std::string& error = extension_load_errors_[extension_id];
    char minidump[256];
    base::debug::Alias(&minidump);
    base::SpanPrintf(minidump, "e::dispatcher:%s:%s", extension_id.c_str(),
                     error.c_str());
    LOG(ERROR) << extension_id << " was never loaded: " << error;
    base::debug::DumpWithoutCrashing();
    return;
  }

  // It's possible that the same extension might generate multiple activation
  // messages, for example from an extension background page followed by an
  // extension subframe on a regular tab.  Ensure that any given extension is
  // only activated once.
  if (IsExtensionActive(extension_id))
    return;

  active_extension_ids_.insert(extension_id);

  DOMActivityLogger::AttachToWorldIfEnabled(DOMActivityLogger::kMainWorldId,
                                            extension_id);

  InitOriginPermissions(extension);

  UpdateActiveExtensions();
}

void Dispatcher::LoadExtensions(
    std::vector<mojom::ExtensionLoadedParamsPtr> loaded_extensions) {
  for (auto& param : loaded_extensions) {
    std::u16string error;
    ExtensionId id = param->id;
    std::optional<base::UnguessableToken> worker_activation_token =
        param->worker_activation_token;
    SetCurrentUserScriptAllowedState(kRendererProfileId, id,
                                     param->user_scripts_allowed);

    scoped_refptr<const Extension> extension =
        ConvertToExtension(std::move(param), kRendererProfileId, &error);
    if (!extension.get()) {
      // Note: in tests |param.id| has been observed to be empty (see comment
      // just below) so this isn't all that reliable.
      NOTREACHED() << error;
    }

    RendererExtensionRegistry* extension_registry =
        RendererExtensionRegistry::Get();

    // The order of setting the token before inserting the extension is
    // intentional so that DidInitializeServiceWorkerContextOnWorkerThread()
    // will pause the worker evaluation
    // (WillEvaluateServiceWorkerOnWorkerThread()) from running before we have
    // these two necessary pieces of information for setting up the extension
    // bindings.
    if (worker_activation_token.has_value()) {
      extension_registry->SetWorkerActivationToken(
          extension, std::move(*worker_activation_token));
    }
    // TODO(jlulejian): This is deliberately a CHECK because other parts of the
    // renderer assume that an extension has been loaded (e.g. specifically
    // worker script evaluation process). If this fails, other CHECK()s and
    // logic will fail too.
    CHECK(extension_registry->Insert(extension));

    unloaded_extensions_.erase(extension->id());

    ExtensionsRendererClient::Get()->OnExtensionLoaded(*extension);

    // Resume service worker if it is suspended.
    {
      base::AutoLock lock(service_workers_paused_for_on_loaded_message_lock_);
      auto it =
          service_workers_paused_for_on_loaded_message_.find(extension->id());
      if (it != service_workers_paused_for_on_loaded_message_.end()) {
        // It's possible that LoadExtensions is called multiple times for the
        // same extension before the worker thread has a chance to run the
        // ResumeEvaluationOnWorkerThread task. In that case, the task_runner
        // will have already been moved, so we need to check if it's valid.
        if (it->second->task_runner) {
          scoped_refptr<base::SingleThreadTaskRunner> task_runner =
              std::move(it->second->task_runner);
          // Using base::Unretained() should be fine as this won't get
          // destructed.
          task_runner->PostTask(
              FROM_HERE,
              base::BindOnce(&Dispatcher::ResumeEvaluationOnWorkerThread,
                             base::Unretained(this), extension->id()));
        }
      }
    }
  }

  // Update the available bindings for all contexts. These may have changed if
  // an externally_connectable extension was loaded that can connect to an
  // open webpage.
  UpdateAllBindings(/*api_permissions_changed=*/false);
}

void Dispatcher::UnloadExtension(const ExtensionId& extension_id) {
  TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::UnloadExtension", extension_id);

  CHECK(!extension_id.empty());
  // An extension should be in the registry if we are unloading it. Otherwise we
  // might be doing something out of the expected order.
  CHECK(RendererExtensionRegistry::Get()->Remove(extension_id));

  unloaded_extensions_.insert(extension_id);

  ExtensionsRendererClient::Get()->OnExtensionUnloaded(extension_id);

  bindings_system_->OnExtensionRemoved(extension_id);

  active_extension_ids_.erase(extension_id);

  user_script_set_manager_->OnExtensionUnloaded(extension_id);

  script_injection_manager_->OnExtensionUnloaded(extension_id);

  // If the extension is later reloaded with a different set of permissions,
  // we'd like it to get a new isolated world ID, so that it can pick up the
  // changed origin allowlist.
  IsolatedWorldManager::GetInstance().RemoveIsolatedWorlds(extension_id);

  // Inform the bindings system that the contexts will be removed to allow time
  // to clear out context-specific data, and then remove the contexts
  // themselves.
  script_context_set_->ForEach(
      GenerateHostIdFromExtensionId(extension_id), nullptr,
      base::BindRepeating(
          &NativeExtensionBindingsSystem::WillReleaseScriptContext,
          base::Unretained(bindings_system_.get())));
  script_context_set_->OnExtensionUnloaded(extension_id);

  // Update the available bindings for the remaining contexts. These may have
  // changed if an externally_connectable extension is unloaded and a webpage
  // is no longer accessible.
  UpdateAllBindings(/*api_permissions_changed=*/false);

  // Invalidates the messages map for the extension in case the extension is
  // reloaded with a new messages map.
  SharedL10nMap::GetInstance().EraseMessagesMap(extension_id);

  // Update the origin access map so that any content scripts injected no longer
  // have dedicated allow/block lists for extra origins.
  WebSecurityPolicy::ClearOriginAccessListForOrigin(
      Extension::GetBaseURLFromExtensionId(extension_id));

  // We don't do anything with existing platform-app stylesheets. They will
  // stay resident, but the URL pattern corresponding to the unloaded
  // extension's URL just won't match anything anymore.
}

void Dispatcher::SuspendExtension(
    const ExtensionId& extension_id,
    mojom::Renderer::SuspendExtensionCallback callback) {
  TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::SuspendExtension", extension_id);

  CHECK(!extension_id.empty());
  // Dispatch the suspend event. This doesn't go through the standard event
  // dispatch machinery because it requires special handling. We need to let
  // the browser know when we are starting and stopping the event dispatch, so
  // that it still considers the extension idle despite any activity the suspend
  // event creates.
  DispatchEventHelper(GenerateHostIdFromExtensionId(extension_id),
                      kOnSuspendEvent, base::ListValue(), nullptr);
  std::move(callback).Run();
}

void Dispatcher::CancelSuspendExtension(const ExtensionId& extension_id) {
  CHECK(!extension_id.empty());
  DispatchEventHelper(GenerateHostIdFromExtensionId(extension_id),
                      kOnSuspendCanceledEvent, base::ListValue(), nullptr);
}

void Dispatcher::SetSystemFont(const std::string& font_family,
                               const std::string& font_size) {
  system_font_family_ = font_family;
  system_font_size_ = font_size;
}

void Dispatcher::SetWebViewPartitionID(const std::string& partition_id) {
  // |webview_partition_id_| cannot be changed once set.
  CHECK(!webview_partition_id_ || webview_partition_id_ == partition_id);
  webview_partition_id_ = partition_id;
}

void Dispatcher::SetScriptingAllowlist(
    const std::vector<ExtensionId>& extension_ids) {
  CHECK(std::all_of(
      extension_ids.begin(), extension_ids.end(),
      [](const ExtensionId& extension_id) { return !extension_id.empty(); }));
  ExtensionsClient::Get()->SetScriptingAllowlist(extension_ids);
}

void Dispatcher::UpdateDefaultPolicyHostRestrictions(
    URLPatternSet default_policy_blocked_hosts,
    URLPatternSet default_policy_allowed_hosts) {
  PermissionsData::SetDefaultPolicyHostRestrictions(
      kRendererProfileId, default_policy_blocked_hosts,
      default_policy_allowed_hosts);
  // Update blink host permission allowlist exceptions for all loaded
  // extensions.
  for (const ExtensionId& extension_id :
       RendererExtensionRegistry::Get()->GetIDs()) {
    const Extension* extension =
        RendererExtensionRegistry::Get()->GetByID(extension_id);
    if (extension->permissions_data()->UsesDefaultPolicyHostRestrictions()) {
      UpdateOriginPermissions(*extension);
    }
  }
  UpdateAllBindings(/*api_permissions_changed=*/false);
}

void Dispatcher::UpdateUserScriptWorlds(
    std::vector<mojom::UserScriptWorldInfoPtr> infos) {
  for (const auto& info : infos) {
    IsolatedWorldManager::GetInstance().SetUserScriptWorldProperties(
        info->extension_id, info->world_id, info->csp, info->enable_messaging);
  }
}

void Dispatcher::ClearUserScriptWorldConfig(
    const ExtensionId& extension_id,
    const std::optional<std::string>& world_id) {
  CHECK(!extension_id.empty());
  IsolatedWorldManager::GetInstance().ClearUserScriptWorldProperties(
      extension_id, world_id);
}

void Dispatcher::UpdateUserHostRestrictions(URLPatternSet user_blocked_hosts,
                                            URLPatternSet user_allowed_hosts) {
  PermissionsData::SetUserHostRestrictions(kRendererProfileId,
                                           std::move(user_blocked_hosts),
                                           std::move(user_allowed_hosts));

  // TODO(crbug.com/40803363): Update origin permissions and bindings as
  // we do with policy host restrictions above.  Currently, user host
  // restrictions aren't used in the origin access allowlist, so there's no
  // point in updating it.
}

void Dispatcher::UpdateTabSpecificPermissions(const ExtensionId& extension_id,
                                              URLPatternSet new_hosts,
                                              int tab_id,
                                              bool update_origin_allowlist) {
  CHECK(!extension_id.empty());
  const Extension* extension =
      RendererExtensionRegistry::Get()->GetByID(extension_id);
  if (!extension)
    return;

  extension->permissions_data()->UpdateTabSpecificPermissions(
      tab_id, PermissionSet(APIPermissionSet(), ManifestPermissionSet(),
                            new_hosts.Clone(), new_hosts.Clone()));

  if (update_origin_allowlist)
    UpdateOriginPermissions(*extension);
}

void Dispatcher::UpdateUserScripts(
    base::ReadOnlySharedMemoryRegion shared_memory,
    mojom::HostIDPtr host_id) {
  TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::UpdateUserScripts", host_id->id);
  user_script_set_manager_->OnUpdateUserScripts(std::move(shared_memory),
                                                *host_id);
}

void Dispatcher::ClearTabSpecificPermissions(
    const std::vector<ExtensionId>& extension_ids,
    int tab_id,
    bool update_origin_allowlist) {
  for (const ExtensionId& id : extension_ids) {
    CHECK(!id.empty());
    const Extension* extension = RendererExtensionRegistry::Get()->GetByID(id);
    if (extension) {
      extension->permissions_data()->ClearTabSpecificPermissions(tab_id);
      if (update_origin_allowlist)
        UpdateOriginPermissions(*extension);
    }
  }
}

void Dispatcher::WatchPages(const std::vector<std::string>& css_selectors) {
  DCHECK(content_watcher_);
  content_watcher_->OnWatchPages(css_selectors);
}

void Dispatcher::DispatchEvent(mojom::DispatchEventParamsPtr params,
                               base::ListValue event_args,
                               DispatchEventCallback callback) {
  CHECK_EQ(params->worker_thread_id, kMainThreadId);
  CHECK(params->host_id);
  content::RenderFrame* background_frame = nullptr;
  if (params->host_id->type == mojom::HostID::HostType::kExtensions) {
    background_frame = ExtensionFrameHelper::GetBackgroundPageFrame(
        GenerateExtensionIdFromHostId(*params->host_id));
  }
  ScriptContext* background_context = nullptr;
  if (background_frame) {
    background_context =
        ScriptContextSet::GetMainWorldContextForFrame(background_frame);
  }
  bool event_has_listener_in_background_context =
      background_context && bindings_system_->HasEventListenerInContext(
                                params->event_name, background_context);

  // Synthesize a user gesture if this was in response to user action; this is
  // necessary if the gesture was e.g. by clicking on the extension toolbar
  // icon, context menu entry, etc.
  //
  // This will only add an active user gesture for the background page, so any
  // listeners in different frames (like a popup or tab) won't be able to use
  // the user gesture. This is intentional, since frames other than the
  // background page should have their own user gestures, such as through button
  // clicks.
  if (params->is_user_gesture && background_context &&
      event_has_listener_in_background_context) {
    background_frame->GetWebFrame()->NotifyUserActivation(
        blink::mojom::UserActivationNotificationType::kExtensionEvent);
  }

  DispatchEventHelper(*params->host_id, params->event_name, event_args,
                      std::move(params->filtering_info));
  std::move(callback).Run(event_has_listener_in_background_context);
}

void Dispatcher::SetDeveloperMode(bool current_developer_mode) {
  SetCurrentDeveloperMode(kRendererProfileId, current_developer_mode);
  // Since this affects the availability of different APIs, we indicate that
  // api permissions may have changed.
  UpdateAllBindings(/*api_permissions_changed=*/true);
}

void Dispatcher::SetUserScriptsAllowed(const ExtensionId& extension_id,
                                       bool enabled) {
  CHECK(!extension_id.empty());
  SetCurrentUserScriptAllowedState(kRendererProfileId, extension_id, enabled);
  const Extension* extension =
      RendererExtensionRegistry::Get()->GetByID(extension_id);
  if (!extension) {
    return;
  }
  UpdateBindingsForExtension(*extension);
}

void Dispatcher::SetSessionInfo(version_info::Channel channel,
                                mojom::FeatureSessionType session_type) {
  SetCurrentChannel(channel);
  SetCurrentFeatureSessionType(session_type);
}

void Dispatcher::ShouldSuspend(ShouldSuspendCallback callback) {
  std::move(callback).Run();
}

void Dispatcher::TransferBlobs(TransferBlobsCallback callback) {
  std::move(callback).Run();
}

void Dispatcher::UpdatePermissions(const ExtensionId& extension_id,
                                   PermissionSet active_permissions,
                                   PermissionSet withheld_permissions,
                                   URLPatternSet policy_blocked_hosts,
                                   URLPatternSet policy_allowed_hosts,
                                   bool uses_default_policy_host_restrictions) {
  CHECK(!extension_id.empty());
  const Extension* extension =
      RendererExtensionRegistry::Get()->GetByID(extension_id);
  if (!extension)
    return;

  if (uses_default_policy_host_restrictions) {
    extension->permissions_data()->SetUsesDefaultHostRestrictions();
  } else {
    extension->permissions_data()->SetPolicyHostRestrictions(
        policy_blocked_hosts, policy_allowed_hosts);
  }

  extension->permissions_data()->SetPermissions(
      std::make_unique<const PermissionSet>(std::move(active_permissions)),
      std::make_unique<const PermissionSet>(std::move(withheld_permissions)));

  UpdateOriginPermissions(*extension);

  UpdateBindingsForExtension(*extension);
}

void Dispatcher::SetActivityLoggingEnabled(bool enabled) {
  activity_logging_enabled_ = enabled;
  if (enabled) {
    UpdateDOMActivityLogging();
  }
  // TODO(crbug.com/490650360): Remove these setter calls as the activity
  // logging state no longer needs to be managed in these objects.
  script_injection_manager_->set_activity_logging_enabled(enabled);
  user_script_set_manager_->set_activity_logging_enabled(enabled);
}

void Dispatcher::SetPolicyActivityLoggingEnabled(bool enabled) {
  ExtensionsRendererClient::Get()->SetPolicyActivityLoggingEnabled(enabled);
  if (enabled) {
    UpdateDOMActivityLogging();
  }
}

void Dispatcher::UpdateDOMActivityLogging() {
  for (const ExtensionId& id : active_extension_ids_) {
    DOMActivityLogger::AttachToWorldIfEnabled(DOMActivityLogger::kMainWorldId,
                                              id);
  }
}

void Dispatcher::OnUserScriptsUpdated(const mojom::HostID& changed_host) {
  // Update the set of active extensions if `changed_host` is an extension and
  // it has scripts.
  if (changed_host.type == mojom::HostID::HostType::kExtensions)
    UpdateActiveExtensions();
}

ScriptContextSetIterable* Dispatcher::GetScriptContextSet() {
  return script_context_set_iterator();
}

void Dispatcher::UpdateActiveExtensions() {
  std::set<ExtensionId> active_extensions = active_extension_ids_;
  user_script_set_manager_->GetAllActiveExtensionIds(&active_extensions);

  // In single-process mode, the browser process reports the active extensions.
  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
          ::switches::kSingleProcess)) {
    std::set<std::string> active_component_extension_names;
    for (const ExtensionId& id : active_extensions) {
      const Extension* extension =
          RendererExtensionRegistry::Get()->GetByID(id);
      if (extension &&
          extension->location() == mojom::ManifestLocation::kComponent) {
        active_component_extension_names.insert(extension->name());
      }
    }
    crash_keys::SetActiveExtensions(active_extensions);
    crash_keys::SetActiveComponentExtensions(active_component_extension_names);
  }
}

void Dispatcher::InitOriginPermissions(const Extension* extension) {
  UpdateOriginPermissions(*extension);
}

void Dispatcher::UpdateOriginPermissions(const Extension& extension) {
  // Remove all old patterns associated with this extension.
  WebSecurityPolicy::ClearOriginAccessListForOrigin(extension.url());

  std::vector<network::mojom::CorsOriginPatternPtr> allow_list =
      CreateCorsOriginAccessAllowList(extension);
  ExtensionsClient::Get()->AddOriginAccessPermissions(
      extension, IsExtensionActive(extension.id()), &allow_list);
  for (const auto& entry : allow_list) {
    WebSecurityPolicy::AddOriginAccessAllowListEntry(
        extension.url(), WebString::FromUtf8(entry->protocol),
        WebString::FromUtf8(entry->domain), entry->port,
        entry->domain_match_mode, entry->port_match_mode, entry->priority);
  }

  for (const auto& entry : CreateCorsOriginAccessBlockList(extension)) {
    WebSecurityPolicy::AddOriginAccessBlockListEntry(
        extension.url(), WebString::FromUtf8(entry->protocol),
        WebString::FromUtf8(entry->domain), entry->port,
        entry->domain_match_mode, entry->port_match_mode, entry->priority);
  }
}

void Dispatcher::EnableCustomElementAllowlist() {
#if BUILDFLAG(IS_CHROMEOS)
  blink::WebCustomElement::AddEmbedderCustomElementName("appview");
#endif
  blink::WebCustomElement::AddEmbedderCustomElementName("extensionoptions");
  blink::WebCustomElement::AddEmbedderCustomElementName("webview");
  for (const auto& api_provider : api_providers_) {
    api_provider->EnableCustomElementAllowlist();
  }
}

void Dispatcher::UpdateAllBindings(bool api_permissions_changed) {
  bindings_system_->UpdateBindings(ExtensionId() /* all contexts */,
                                   api_permissions_changed,
                                   script_context_set_iterator());

  WorkerThreadDispatcher::Get()->UpdateAllServiceWorkerBindings();
}

void Dispatcher::UpdateBindingsForExtension(const Extension& extension) {
  bindings_system_->UpdateBindings(extension.id(),
                                   true /* permissions_changed */,
                                   script_context_set_iterator());

  // Update Service Worker bindings too, if applicable.
  if (!BackgroundInfo::IsServiceWorkerBased(&extension))
    return;

  const bool updated =
      WorkerThreadDispatcher::Get()->UpdateBindingsForWorkers(extension.id());
  // TODO(lazyboy): When can this fail?
  DCHECK(updated) << "Some or all workers failed to update bindings.";
}

// NOTE: please use the naming convention "foo_natives" for these.
void Dispatcher::RegisterNativeHandlers(
    ModuleSystem* module_system,
    ScriptContext* context,
    NativeExtensionBindingsSystem* bindings_system,
    V8SchemaRegistry* v8_schema_registry) {
  for (const auto& api_provider : api_providers_) {
    api_provider->RegisterNativeHandlers(module_system, bindings_system,
                                         v8_schema_registry, context);
  }
}

void Dispatcher::PopulateSourceMap() {
  for (const auto& api_provider : api_providers_) {
    api_provider->PopulateSourceMap(&source_map_);
  }
}

bool Dispatcher::IsWithinPlatformApp() {
  for (auto iter = active_extension_ids_.begin();
       iter != active_extension_ids_.end(); ++iter) {
    const Extension* extension =
        RendererExtensionRegistry::Get()->GetByID(*iter);
    if (extension && extension->is_platform_app())
      return true;
  }
  return false;
}

#if BUILDFLAG(ENABLE_GUEST_VIEW)
void Dispatcher::RequireGuestViewModules(ScriptContext* context) {
  ModuleSystem* module_system = context->module_system();
  bool requires_guest_view_module = false;

  // This determines whether to register error-providing custom elements for the
  // GuestView types that are not available. We only do this in contexts where
  // it is possible to gain access to a given GuestView element by declaring the
  // necessary permission in a manifest file. We don't want to define
  // error-providing elements in other extension contexts as the names could
  // collide with names used in the extension. Also, WebUIs may be allowlisted
  // to use GuestViews, but we don't define the error-providing elements in this
  // case.
  const bool is_platform_app =
      context->context_type() == mojom::ContextType::kPrivilegedExtension &&
      !context->IsForServiceWorker() && context->extension() &&
      context->extension()->is_platform_app();
  // The webview permission is also available to internal allowlisted
  // extensions, but not to extensions in general.
  const bool web_view_permission_exists = is_platform_app;

  // TODO(fsamuel): Eagerly calling Require on context startup is expensive.
  // It would be better if there were a light way of detecting when a webview
  // or appview is created and only then set up the infrastructure.

#if BUILDFLAG(IS_CHROMEOS)
  const bool app_view_permission_exists = is_platform_app;
  // Require AppView.
  if (context->GetAvailability("appViewEmbedderInternal").is_available()) {
    requires_guest_view_module = true;
    module_system->Require("appViewElement");
  } else if (app_view_permission_exists) {
    module_system->Require("appViewDeny");
  }
#endif

  // Require ExtensionOptions.
  if (context->GetAvailability("extensionOptionsInternal").is_available()) {
    requires_guest_view_module = true;
    module_system->Require("extensionOptionsElement");
  }

  // Require WebView.
  if (context->GetAvailability("webViewInternal").is_available()) {
    requires_guest_view_module = true;

    for (const auto& api_provider : api_providers_) {
      api_provider->RequireWebViewModules(context);
    }
  } else if (web_view_permission_exists) {
    module_system->Require("webViewDeny");
  }

  if (requires_guest_view_module) {
    // If a frame has guest view custom elements defined, we need to make sure
    // the custom elements are also defined in subframes. The subframes will
    // need a scripting context which we will need to forcefully create if
    // the subframe doesn't otherwise have any scripts.
    context->web_frame()
        ->View()
        ->GetSettings()
        ->SetForceMainWorldInitialization(true);
  }
}
#endif  // BUILDFLAG(ENABLE_GUEST_VIEW)

std::unique_ptr<NativeExtensionBindingsSystem> Dispatcher::CreateBindingsSystem(
    NativeExtensionBindingsSystem::Delegate* delegate,
    std::unique_ptr<IPCMessageSender> ipc_sender) {
  auto bindings_system = std::make_unique<NativeExtensionBindingsSystem>(
      delegate, std::move(ipc_sender));
  for (const auto& api_provider : api_providers_) {
    api_provider->AddBindingsSystemHooks(this, bindings_system.get());
  }
  return bindings_system;
}

void Dispatcher::ResumeEvaluationOnWorkerThread(
    const ExtensionId& extension_id) {
  base::AutoLock lock(service_workers_paused_for_on_loaded_message_lock_);
  auto it = service_workers_paused_for_on_loaded_message_.find(extension_id);
  if (it != service_workers_paused_for_on_loaded_message_.end()) {
    blink::WebServiceWorkerContextProxy* context_proxy =
        it->second->context_proxy;
    context_proxy->ResumeEvaluation();
    service_workers_paused_for_on_loaded_message_.erase(it);
  }
}

}  // namespace extensions
