// 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/browser/api/runtime/runtime_api.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/notreached.h"
#include "base/one_shot_event.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/values.h"
#include "base/version.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/child_process_security_policy.h"
#include "extensions/browser/api/runtime/runtime_api_delegate.h"
#include "extensions/browser/blocklist_extension_prefs.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/events/lazy_event_dispatch_util.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/lazy_context_id.h"
#include "extensions/browser/lazy_context_task_queue.h"
#include "extensions/browser/process_manager_factory.h"
#include "extensions/browser/view_type_utils.h"
#include "extensions/common/api/runtime.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/shared_module_info.h"
#include "extensions/common/mojom/view_type.mojom.h"
#include "storage/browser/file_system/isolated_context.h"
#include "url/gurl.h"

#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
#include "chrome/browser/extensions/api/tabs/tabs_api.h"
#include "extensions/common/manifest_handlers/options_page_info.h"
#endif

using content::BrowserContext;

namespace extensions {

namespace runtime = api::runtime;

namespace {

const char kNoBackgroundPageError[] = "You do not have a background page.";
const char kPageLoadError[] = "Background page failed to load.";
const char kFailedToCreateOptionsPage[] = "Could not create an options page.";
const char kInstallId[] = "id";
const char kInstallReason[] = "reason";
const char kInstallReasonChromeUpdate[] = "chrome_update";
const char kInstallReasonUpdate[] = "update";
const char kInstallReasonInstall[] = "install";
const char kInstallReasonSharedModuleUpdate[] = "shared_module_update";
const char kInstallPreviousVersion[] = "previousVersion";
const char kInvalidUrlError[] = "Invalid URL: \"*\".";
const char kPlatformInfoUnavailable[] = "Platform information unavailable.";

const char kUpdatesDisabledError[] = "Autoupdate is not enabled.";

// A preference key storing the url loaded when an extension is uninstalled.
const char kUninstallUrl[] = "uninstall_url";

// The name of the directory to be returned by getPackageDirectoryEntry. This
// particular value does not matter to user code, but is chosen for consistency
// with the equivalent Pepper API.
const char kPackageDirectoryPath[] = "crxfs";

// Preference key for storing the last successful restart due to a call to
// chrome.runtime.restartAfterDelay().
constexpr char kPrefLastRestartAfterDelayTime[] =
    "last_restart_after_delay_time";
// Preference key for storing whether the most recent restart was due to a
// successful call to chrome.runtime.restartAfterDelay().
constexpr char kPrefLastRestartWasDueToDelayedRestartApi[] =
    "last_restart_was_due_to_delayed_restart_api";

// Error and status messages strings for the restartAfterDelay() API.
constexpr char kErrorInvalidArgument[] = "Invalid argument: *.";
constexpr char kErrorOnlyKioskModeAllowed[] =
    "API available only for ChromeOS kiosk mode.";
constexpr char kErrorOnlyFirstExtensionAllowed[] =
    "Not the first extension to call this API.";
constexpr char kErrorRequestedTooSoon[] =
    "Restart was requested too soon. It was throttled instead.";

constexpr int kMinDurationBetweenSuccessiveRestartsHours = 3;

// This is used for unit tests, so that we can test the restartAfterDelay
// API without a kiosk app.
bool allow_non_kiosk_apps_restart_api_for_test = false;

void DispatchOnStartupEventImpl(
    BrowserContext* browser_context,
    const ExtensionId& extension_id,
    bool first_call,
    std::unique_ptr<LazyContextTaskQueue::ContextInfo> context_info) {
  // A NULL ContextInfo from the task callback means the page failed
  // to load. Give up.
  if (!context_info && !first_call) {
    return;
  }

  if (ExtensionsBrowserClient::Get()->IsShuttingDown() ||
      !ExtensionsBrowserClient::Get()->IsValidContext(browser_context)) {
    return;
  }

  // Don't send onStartup events to incognito browser contexts.
  if (browser_context->IsOffTheRecord()) {
    return;
  }

  ExtensionSystem* system = ExtensionSystem::Get(browser_context);
  if (!system) {
    return;
  }

  // If this is a persistent background page, we want to wait for it to load
  // (it might not be ready, since this is startup). But only enqueue once.
  // If it fails to load the first time, don't bother trying again.
  const Extension* extension = ExtensionRegistry::Get(browser_context)
                                   ->enabled_extensions()
                                   .GetByID(extension_id);
  if (extension && BackgroundInfo::HasPersistentBackgroundPage(extension) &&
      first_call) {
    const auto context_id =
        LazyContextId::ForExtension(browser_context, extension);
    LazyContextTaskQueue* task_queue = context_id.GetTaskQueue();
    if (task_queue->ShouldEnqueueTask(browser_context, extension)) {
      task_queue->AddPendingTask(
          context_id, base::BindOnce(&DispatchOnStartupEventImpl,
                                     browser_context, extension_id, false));
      return;
    }
  }

  auto event = std::make_unique<Event>(events::RUNTIME_ON_STARTUP,
                                       runtime::OnStartup::kEventName,
                                       base::Value::List());
  EventRouter::Get(browser_context)
      ->DispatchEventToExtension(extension_id, std::move(event));
}

std::string GetUninstallURL(ExtensionPrefs* prefs,
                            const ExtensionId& extension_id) {
  std::string url_string;
  prefs->ReadPrefAsString(extension_id, kUninstallUrl, &url_string);
  return url_string;
}

// Returns true if the given `context` matches the `filter`.
bool ExtensionContextMatchesFilter(
    const api::runtime::ExtensionContext& context,
    const api::runtime::ContextFilter& filter) {
  if (filter.context_types &&
      !base::Contains(*filter.context_types, context.context_type)) {
    return false;
  }
  if (filter.context_ids &&
      !base::Contains(*filter.context_ids, context.context_id)) {
    return false;
  }
  if (filter.tab_ids && !base::Contains(*filter.tab_ids, context.tab_id)) {
    return false;
  }
  if (filter.window_ids &&
      !base::Contains(*filter.window_ids, context.window_id)) {
    return false;
  }
  if (filter.document_ids &&
      (!context.document_id ||
       !base::Contains(*filter.document_ids, *context.document_id))) {
    return false;
  }
  if (filter.frame_ids &&
      !base::Contains(*filter.frame_ids, context.frame_id)) {
    return false;
  }
  if (filter.document_urls &&
      (!context.document_url ||
       !base::Contains(*filter.document_urls, *context.document_url))) {
    return false;
  }
  if (filter.document_origins &&
      (!context.document_origin ||
       !base::Contains(*filter.document_origins, *context.document_origin))) {
    return false;
  }
  if (filter.incognito && *filter.incognito != context.incognito) {
    return false;
  }

  return true;
}

}  // namespace

///////////////////////////////////////////////////////////////////////////////

static base::LazyInstance<
    BrowserContextKeyedAPIFactory<RuntimeAPI>>::DestructorAtExit g_factory =
    LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<RuntimeAPI>* RuntimeAPI::GetFactoryInstance() {
  return g_factory.Pointer();
}

constexpr int RuntimeAPI::kFastReloadCount;
constexpr int RuntimeAPI::kUnpackedFastReloadCount;

// static
void RuntimeAPI::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(kPrefLastRestartWasDueToDelayedRestartApi,
                                false);
  registry->RegisterDoublePref(kPrefLastRestartAfterDelayTime, 0.0);
}

template <>
void BrowserContextKeyedAPIFactory<RuntimeAPI>::DeclareFactoryDependencies() {
  DependsOn(ProcessManagerFactory::GetInstance());
}

RuntimeAPI::RuntimeAPI(content::BrowserContext* context)
    : browser_context_(context),
      minimum_duration_between_restarts_(
          base::Hours(kMinDurationBetweenSuccessiveRestartsHours)),
      dispatch_chrome_updated_event_(false),
      did_read_delayed_restart_preferences_(false),
      was_last_restart_due_to_delayed_restart_api_(false) {
  // RuntimeAPI is redirected in incognito, so |browser_context_| is never
  // incognito.
  DCHECK(!browser_context_->IsOffTheRecord());

  ExtensionSystem::Get(context)->ready().Post(
      FROM_HERE, base::BindOnce(&RuntimeAPI::OnExtensionsReady,
                                weak_ptr_factory_.GetWeakPtr()));
  extension_registry_observation_.Observe(
      ExtensionRegistry::Get(browser_context_));
  process_manager_observation_.Observe(ProcessManager::Get(browser_context_));

  delegate_ = ExtensionsBrowserClient::Get()->CreateRuntimeAPIDelegate(
      browser_context_);

  // Check if registered events are up to date. We can only do this once
  // per browser context, since it updates internal state when called.
  dispatch_chrome_updated_event_ =
      ExtensionsBrowserClient::Get()->DidVersionUpdate(browser_context_);

  EventRouter::Get(browser_context_)
      ->lazy_event_dispatch_util()
      ->AddObserver(this);
}

RuntimeAPI::~RuntimeAPI() = default;

void RuntimeAPI::OnExtensionLoaded(content::BrowserContext* browser_context,
                                   const Extension* extension) {
  if (!dispatch_chrome_updated_event_) {
    return;
  }

  // Dispatch the onInstalled event with reason "chrome_update".
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&RuntimeEventRouter::DispatchOnInstalledEvent,
                                static_cast<void*>(browser_context_),
                                extension->id(), base::Version(), true));
}

void RuntimeAPI::OnExtensionUninstalled(
    content::BrowserContext* browser_context,
    const Extension* extension,
    UninstallReason reason) {
  RuntimeEventRouter::OnExtensionUninstalled(browser_context_, extension->id(),
                                             reason);
}

void RuntimeAPI::Shutdown() {
  delegate_->RemoveUpdateObserver(this);
  EventRouter::Get(browser_context_)
      ->lazy_event_dispatch_util()
      ->RemoveObserver(this);
}

void RuntimeAPI::OnAppUpdateAvailable(const Extension& extension) {
  RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
      browser_context_, extension.id(), extension.manifest()->value());
}

void RuntimeAPI::OnChromeUpdateAvailable() {
  LOG(INFO) << "RuntimeAPI OnBrowserUpdateAvailable called";
  RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(browser_context_);
}

void RuntimeAPI::OnBackgroundHostStartup(const Extension* extension) {
  RuntimeEventRouter::DispatchOnStartupEvent(browser_context_, extension->id());
}

void RuntimeAPI::ReloadExtension(const ExtensionId& extension_id) {
  delegate_->ReloadExtension(extension_id);
}

bool RuntimeAPI::CheckForUpdates(
    const ExtensionId& extension_id,
    RuntimeAPIDelegate::UpdateCheckCallback callback) {
  return delegate_->CheckForUpdates(extension_id, std::move(callback));
}

void RuntimeAPI::OpenURL(const GURL& update_url) {
  delegate_->OpenURL(update_url);
}

bool RuntimeAPI::GetPlatformInfo(runtime::PlatformInfo* info) {
  return delegate_->GetPlatformInfo(info);
}

bool RuntimeAPI::RestartDevice(std::string* error_message) {
  if (was_last_restart_due_to_delayed_restart_api_ &&
      (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode() ||
       allow_non_kiosk_apps_restart_api_for_test)) {
    // We don't allow an app by calling chrome.runtime.restart() to clear the
    // throttle enforced on it when calling chrome.runtime.restartAfterDelay(),
    // i.e. the app can't unthrottle itself.
    // When running in forced kiosk app mode, we assume the following restart
    // request will succeed.
    PrefService* pref_service =
        ExtensionsBrowserClient::Get()->GetPrefServiceForContext(
            browser_context_);
    DCHECK(pref_service);
    pref_service->SetBoolean(kPrefLastRestartWasDueToDelayedRestartApi, true);
  }
  return delegate_->RestartDevice(error_message);
}

RuntimeAPI::RestartAfterDelayStatus RuntimeAPI::RestartDeviceAfterDelay(
    const ExtensionId& extension_id,
    int seconds_from_now) {
  // To achieve as much accuracy as possible, record the time of the call as
  // |now| here.
  const base::Time now = base::Time::NowFromSystemTime();

  if (schedule_restart_first_extension_id_.empty()) {
    schedule_restart_first_extension_id_ = extension_id;
  } else if (extension_id != schedule_restart_first_extension_id_) {
    // We only allow the first extension to call this API to call it repeatedly.
    // Any other extension will fail.
    return RestartAfterDelayStatus::FAILED_NOT_FIRST_EXTENSION;
  }

  MaybeCancelRunningDelayedRestartTimer();

  if (seconds_from_now == -1) {
    // We already stopped the running timer (if any).
    return RestartAfterDelayStatus::SUCCESS_RESTART_CANCELED;
  }

  if (!did_read_delayed_restart_preferences_) {
    // Try to read any previous successful restart attempt time resulting from
    // this API.
    PrefService* pref_service =
        ExtensionsBrowserClient::Get()->GetPrefServiceForContext(
            browser_context_);
    DCHECK(pref_service);

    was_last_restart_due_to_delayed_restart_api_ =
        pref_service->GetBoolean(kPrefLastRestartWasDueToDelayedRestartApi);
    if (was_last_restart_due_to_delayed_restart_api_) {
      // We clear this bit if the previous restart was due to this API, so that
      // we don't throttle restart requests coming after other restarts or
      // shutdowns not caused by the runtime API.
      pref_service->SetBoolean(kPrefLastRestartWasDueToDelayedRestartApi,
                               false);
    }

    last_delayed_restart_time_ = base::Time::FromSecondsSinceUnixEpoch(
        pref_service->GetDouble(kPrefLastRestartAfterDelayTime));

    if (!allow_non_kiosk_apps_restart_api_for_test) {
      // Don't read every time unless in tests.
      did_read_delayed_restart_preferences_ = true;
    }
  }

  return ScheduleDelayedRestart(now, seconds_from_now);
}

bool RuntimeAPI::OpenOptionsPage(const Extension* extension,
                                 content::BrowserContext* browser_context) {
  return delegate_->OpenOptionsPage(extension, browser_context);
}

void RuntimeAPI::MaybeCancelRunningDelayedRestartTimer() {
  if (restart_after_delay_timer_.IsRunning()) {
    restart_after_delay_timer_.Stop();
  }
}

void RuntimeAPI::OnExtensionsReady() {
  // We're done restarting Chrome after an update.
  dispatch_chrome_updated_event_ = false;
  delegate_->AddUpdateObserver(this);
}

RuntimeAPI::RestartAfterDelayStatus RuntimeAPI::ScheduleDelayedRestart(
    const base::Time& now,
    int seconds_from_now) {
  base::TimeDelta delay_till_restart = base::Seconds(seconds_from_now);

  // Throttle restart requests that are received too soon successively, only if
  // the previous restart was due to this API.
  bool was_throttled = false;
  if (was_last_restart_due_to_delayed_restart_api_) {
    base::Time future_restart_time = now + delay_till_restart;
    base::TimeDelta delta_since_last_restart =
        future_restart_time > last_delayed_restart_time_
            ? future_restart_time - last_delayed_restart_time_
            : base::TimeDelta::Max();
    if (delta_since_last_restart < minimum_duration_between_restarts_) {
      // Schedule the restart after |minimum_duration_between_restarts_| has
      // passed.
      delay_till_restart = minimum_duration_between_restarts_ -
                           (now - last_delayed_restart_time_);
      was_throttled = true;
    }
  }

  restart_after_delay_timer_.Start(
      FROM_HERE, delay_till_restart,
      base::BindOnce(&RuntimeAPI::OnDelayedRestartTimerTimeout,
                     weak_ptr_factory_.GetWeakPtr()));

  return was_throttled ? RestartAfterDelayStatus::FAILED_THROTTLED
                       : RestartAfterDelayStatus::SUCCESS_RESTART_SCHEDULED;
}

void RuntimeAPI::OnDelayedRestartTimerTimeout() {
  // We can persist "now" as the last successful restart time, assuming that the
  // following restart request will succeed, since it can only fail if requested
  // by non kiosk apps, and we prevent that from the beginning (unless in
  // unit tests).
  // This assumption is important, since once restart is requested, we might not
  // have enough time to persist the data to disk.
  double now = base::Time::NowFromSystemTime().InSecondsFSinceUnixEpoch();
  PrefService* pref_service =
      ExtensionsBrowserClient::Get()->GetPrefServiceForContext(
          browser_context_);
  DCHECK(pref_service);
  pref_service->SetDouble(kPrefLastRestartAfterDelayTime, now);
  pref_service->SetBoolean(kPrefLastRestartWasDueToDelayedRestartApi, true);

  std::string error_message;
  const bool success = delegate_->RestartDevice(&error_message);

  // Make sure our above assumption is maintained.
  DCHECK(success || allow_non_kiosk_apps_restart_api_for_test);
}

void RuntimeAPI::AllowNonKioskAppsInRestartAfterDelayForTesting() {
  allow_non_kiosk_apps_restart_api_for_test = true;
}

///////////////////////////////////////////////////////////////////////////////

// static
void RuntimeEventRouter::DispatchOnStartupEvent(
    content::BrowserContext* context,
    const ExtensionId& extension_id) {
  DispatchOnStartupEventImpl(context, extension_id, true, nullptr);
}

// static
void RuntimeEventRouter::DispatchOnInstalledEvent(
    void* context_id,
    const ExtensionId& extension_id,
    const base::Version& old_version,
    bool chrome_updated) {
  if (!ExtensionsBrowserClient::Get()->IsValidContext(context_id)) {
    return;
  }
  content::BrowserContext* context =
      reinterpret_cast<content::BrowserContext*>(context_id);
  ExtensionSystem* system = ExtensionSystem::Get(context);
  if (!system) {
    return;
  }

  // Only dispatch runtime.onInstalled events if:
  // 1. the extension has just been installed/updated
  // 2. chrome has updated and the extension had runtime.onInstalled listener.
  // TODO(devlin): Having the chrome_update event tied to onInstalled has caused
  // some issues in the past, see crbug.com/451268. We might want to eventually
  // decouple the chrome_updated event from onInstalled and/or throttle
  // dispatching the chrome_updated event.
  if (chrome_updated && !EventRouter::Get(context)->ExtensionHasEventListener(
                            extension_id, runtime::OnInstalled::kEventName)) {
    return;
  }

  base::Value::List event_args;
  base::Value::Dict info;
  if (old_version.IsValid()) {
    info.Set(kInstallReason, kInstallReasonUpdate);
    info.Set(kInstallPreviousVersion, old_version.GetString());
  } else if (chrome_updated) {
    info.Set(kInstallReason, kInstallReasonChromeUpdate);
  } else {
    info.Set(kInstallReason, kInstallReasonInstall);
  }
  event_args.Append(std::move(info));
  EventRouter* event_router = EventRouter::Get(context);
  DCHECK(event_router);
  auto event = std::make_unique<Event>(events::RUNTIME_ON_INSTALLED,
                                       runtime::OnInstalled::kEventName,
                                       std::move(event_args));
  event_router->DispatchEventWithLazyListener(extension_id, std::move(event));

  if (old_version.IsValid()) {
    const Extension* extension =
        ExtensionRegistry::Get(context)->enabled_extensions().GetByID(
            extension_id);
    if (extension && SharedModuleInfo::IsSharedModule(extension)) {
      std::unique_ptr<ExtensionSet> dependents =
          system->GetDependentExtensions(extension);
      for (ExtensionSet::const_iterator i = dependents->begin();
           i != dependents->end(); i++) {
        base::Value::List sm_event_args;
        base::Value::Dict sm_info;
        sm_info.Set(kInstallReason, kInstallReasonSharedModuleUpdate);
        sm_info.Set(kInstallPreviousVersion, old_version.GetString());
        sm_info.Set(kInstallId, extension_id);
        sm_event_args.Append(std::move(sm_info));
        auto sm_event = std::make_unique<Event>(
            events::RUNTIME_ON_INSTALLED, runtime::OnInstalled::kEventName,
            std::move(sm_event_args));
        event_router->DispatchEventWithLazyListener((*i)->id(),
                                                    std::move(sm_event));
      }
    }
  }
}

// static
void RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
    content::BrowserContext* context,
    const ExtensionId& extension_id,
    const base::Value::Dict* manifest) {
  ExtensionSystem* system = ExtensionSystem::Get(context);
  if (!system) {
    return;
  }

  base::Value::List args;
  args.Append(manifest->Clone());
  EventRouter* event_router = EventRouter::Get(context);
  DCHECK(event_router);
  auto event = std::make_unique<Event>(events::RUNTIME_ON_UPDATE_AVAILABLE,
                                       runtime::OnUpdateAvailable::kEventName,
                                       std::move(args));
  event_router->DispatchEventToExtension(extension_id, std::move(event));
}

// static
void RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(
    content::BrowserContext* context) {
  ExtensionSystem* system = ExtensionSystem::Get(context);
  if (!system) {
    return;
  }

  EventRouter* event_router = EventRouter::Get(context);
  DCHECK(event_router);
  auto event = std::make_unique<Event>(
      events::RUNTIME_ON_BROWSER_UPDATE_AVAILABLE,
      runtime::OnBrowserUpdateAvailable::kEventName, base::Value::List());
  event_router->BroadcastEvent(std::move(event));
  LOG(INFO) << "RuntimeEventRouter API called: runtime.onBrowserUpdateAvailable";
}

// static
void RuntimeEventRouter::DispatchOnRestartRequiredEvent(
    content::BrowserContext* context,
    const std::string& app_id,
    api::runtime::OnRestartRequiredReason reason) {
  ExtensionSystem* system = ExtensionSystem::Get(context);
  if (!system) {
    return;
  }

  std::unique_ptr<Event> event(
      new Event(events::RUNTIME_ON_RESTART_REQUIRED,
                runtime::OnRestartRequired::kEventName,
                api::runtime::OnRestartRequired::Create(reason)));
  EventRouter* event_router = EventRouter::Get(context);
  DCHECK(event_router);
  event_router->DispatchEventToExtension(app_id, std::move(event));
  LOG(INFO) << "RuntimeEventRouter API called: runtime.onRestartRequired";
}

// static
void RuntimeEventRouter::OnExtensionUninstalled(
    content::BrowserContext* context,
    const ExtensionId& extension_id,
    UninstallReason reason) {
  if (!(reason == UNINSTALL_REASON_USER_INITIATED ||
        reason == UNINSTALL_REASON_MANAGEMENT_API ||
        reason == UNINSTALL_REASON_CHROME_WEBSTORE)) {
    return;
  }

  GURL uninstall_url(
      GetUninstallURL(ExtensionPrefs::Get(context), extension_id));

  if (!uninstall_url.SchemeIsHTTPOrHTTPS()) {
    // Previous versions of Chrome allowed non-http(s) URLs to be stored in the
    // prefs. Now they're disallowed, but the old data may still exist.
    return;
  }

  // Blocklisted extensions should not open uninstall_url.
  if (blocklist_prefs::IsExtensionBlocklisted(
          extension_id, extensions::ExtensionPrefs::Get(context))) {
    return;
  }

#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  std::string url = uninstall_url.spec();
  extensions::TabsCreateFunction::CreateTabForExtension(url, context);
#else
  RuntimeAPI::GetFactoryInstance()->Get(context)->OpenURL(uninstall_url);
#endif
}

void RuntimeAPI::OnExtensionInstalledAndLoaded(
    content::BrowserContext* browser_context,
    const Extension* extension,
    const base::Version& previous_version) {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&RuntimeEventRouter::DispatchOnInstalledEvent,
                                static_cast<void*>(browser_context_),
                                extension->id(), previous_version, false));
}

ExtensionFunction::ResponseAction RuntimeGetBackgroundPageFunction::Run() {
  if (!BackgroundInfo::HasBackgroundPage(extension())) {
    return RespondNow(Error(kNoBackgroundPageError));
  }
  ExtensionHost* host = ProcessManager::Get(browser_context())
                            ->GetBackgroundHostForExtension(extension_id());
  const auto context_id =
      LazyContextId::ForExtension(browser_context(), extension());
  LazyContextTaskQueue* task_queue = context_id.GetTaskQueue();
  if (task_queue->ShouldEnqueueTask(browser_context(), extension())) {
    task_queue->AddPendingTask(
        context_id,
        base::BindOnce(&RuntimeGetBackgroundPageFunction::OnPageLoaded, this));
  } else if (host) {
    OnPageLoaded(std::make_unique<LazyContextTaskQueue::ContextInfo>(host));
  } else {
    return RespondNow(Error(kNoBackgroundPageError));
  }

  return RespondLater();
}

void RuntimeGetBackgroundPageFunction::OnPageLoaded(
    std::unique_ptr<LazyContextTaskQueue::ContextInfo> context_info) {
  if (context_info) {
    Respond(NoArguments());
  } else {
    Respond(Error(kPageLoadError));
  }
}

ExtensionFunction::ResponseAction RuntimeOpenOptionsPageFunction::Run() {
  RuntimeAPI* api = RuntimeAPI::GetFactoryInstance()->Get(browser_context());
  return RespondNow(api->OpenOptionsPage(extension(), browser_context())
                        ? NoArguments()
                        : Error(kFailedToCreateOptionsPage));
}

ExtensionFunction::ResponseAction RuntimeSetUninstallURLFunction::Run() {
  std::optional<api::runtime::SetUninstallURL::Params> params =
      api::runtime::SetUninstallURL::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);
  if (!params->url.empty() && !GURL(params->url).SchemeIsHTTPOrHTTPS()) {
    return RespondNow(Error(kInvalidUrlError, params->url));
  }

  ExtensionPrefs::Get(browser_context())
      ->UpdateExtensionPref(extension_id(), kUninstallUrl,
                            base::Value(std::move(params->url)));
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction RuntimeReloadFunction::Run() {
  RuntimeAPI::GetFactoryInstance()
      ->Get(browser_context())
      ->ReloadExtension(extension_id());
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction RuntimeRequestUpdateCheckFunction::Run() {
  if (!RuntimeAPI::GetFactoryInstance()
           ->Get(browser_context())
           ->CheckForUpdates(
               extension_id(),
               base::BindOnce(&RuntimeRequestUpdateCheckFunction::CheckComplete,
                              this))) {
    return RespondNow(Error(kUpdatesDisabledError));
  }
  return RespondLater();
}

void RuntimeRequestUpdateCheckFunction::CheckComplete(
    const RuntimeAPIDelegate::UpdateCheckResult& result) {
  api::runtime::RequestUpdateCheck::Results::Result return_result;
  return_result.status = result.status;
  return_result.version = std::optional<std::string>(result.version);
  Respond(WithArguments(return_result.ToValue()));
}

ExtensionFunction::ResponseAction RuntimeRestartFunction::Run() {
  std::string message;
  bool result = RuntimeAPI::GetFactoryInstance()
                    ->Get(browser_context())
                    ->RestartDevice(&message);
  if (!result) {
    return RespondNow(Error(message));
  }
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction RuntimeRestartAfterDelayFunction::Run() {
  if (!allow_non_kiosk_apps_restart_api_for_test &&
      !ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode()) {
    return RespondNow(Error(kErrorOnlyKioskModeAllowed));
  }

  std::optional<api::runtime::RestartAfterDelay::Params> params =
      api::runtime::RestartAfterDelay::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);
  int seconds = params->seconds;

  if (seconds <= 0 && seconds != -1) {
    return RespondNow(
        Error(kErrorInvalidArgument, base::NumberToString(seconds)));
  }

  RuntimeAPI::RestartAfterDelayStatus request_status =
      RuntimeAPI::GetFactoryInstance()
          ->Get(browser_context())
          ->RestartDeviceAfterDelay(extension()->id(), seconds);

  switch (request_status) {
    case RuntimeAPI::RestartAfterDelayStatus::FAILED_NOT_FIRST_EXTENSION:
      return RespondNow(Error(kErrorOnlyFirstExtensionAllowed));

    case RuntimeAPI::RestartAfterDelayStatus::FAILED_THROTTLED:
      return RespondNow(Error(kErrorRequestedTooSoon));

    case RuntimeAPI::RestartAfterDelayStatus::SUCCESS_RESTART_CANCELED:
    case RuntimeAPI::RestartAfterDelayStatus::SUCCESS_RESTART_SCHEDULED:
      return RespondNow(NoArguments());
  }

  NOTREACHED();
}

ExtensionFunction::ResponseAction RuntimeGetPlatformInfoFunction::Run() {
  runtime::PlatformInfo info;
  if (!RuntimeAPI::GetFactoryInstance()
           ->Get(browser_context())
           ->GetPlatformInfo(&info)) {
    return RespondNow(Error(kPlatformInfoUnavailable));
  }
  return RespondNow(
      ArgumentList(runtime::GetPlatformInfo::Results::Create(info)));
}

ExtensionFunction::ResponseAction
RuntimeGetPackageDirectoryEntryFunction::Run() {
  storage::IsolatedContext* isolated_context =
      storage::IsolatedContext::GetInstance();
  DCHECK(isolated_context);

  std::string relative_path = kPackageDirectoryPath;
  base::FilePath path = extension_->path();
  storage::IsolatedContext::ScopedFSHandle filesystem =
      isolated_context->RegisterFileSystemForPath(
          storage::kFileSystemTypeLocal, std::string(), path, &relative_path);

  content::ChildProcessSecurityPolicy* policy =
      content::ChildProcessSecurityPolicy::GetInstance();
  policy->GrantReadFileSystem(source_process_id(), filesystem.id());
  base::Value::Dict dict;
  dict.Set("fileSystemId", filesystem.id());
  dict.Set("baseName", relative_path);
  return RespondNow(WithArguments(std::move(dict)));
}

RuntimeGetContextsFunction::RuntimeGetContextsFunction() = default;
RuntimeGetContextsFunction::~RuntimeGetContextsFunction() = default;

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

  auto params = api::runtime::GetContexts::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);
  const api::runtime::ContextFilter& filter = params->filter;

  std::vector<api::runtime::ExtensionContext> result;

  // Minor optimization: only construct the context if there's a chance it will
  // match the filter.
  if (!filter.context_types ||
      base::Contains(*filter.context_types,
                     api::runtime::ContextType::kBackground)) {
    if (std::optional<api::runtime::ExtensionContext> worker =
            GetWorkerContext()) {
      result.push_back(std::move(*worker));
    }
  }

  std::vector<api::runtime::ExtensionContext> frame_contexts =
      GetFrameContexts();
  result.insert(result.end(), std::make_move_iterator(frame_contexts.begin()),
                std::make_move_iterator(frame_contexts.end()));

  // Erase any contexts that don't match the specified filter.
  std::erase_if(result,
                [&filter](const api::runtime::ExtensionContext& context) {
                  return !ExtensionContextMatchesFilter(context, filter);
                });

  return RespondNow(
      ArgumentList(api::runtime::GetContexts::Results::Create(result)));
}

std::optional<api::runtime::ExtensionContext>
RuntimeGetContextsFunction::GetWorkerContext() {
  ProcessManager* const process_manager =
      ProcessManager::Get(browser_context());
  DCHECK(process_manager);

  std::vector<WorkerId> active_workers =
      process_manager->GetServiceWorkersForExtension(extension()->id());

  // TODO(crbug.com/40936639):Enable this CHECK once multiple active workers is
  // resolved.
  // CHECK_LE(active_workers.size(), 1u)
  //     << "runtime.getContexts() API call found more than one service worker "
  //        "for extension.";

  if (active_workers.empty()) {
    return std::nullopt;
  }

  api::runtime::ExtensionContext context;
  context.context_type = api::runtime::ContextType::kBackground;
  base::Uuid context_id =
      process_manager->GetContextIdForWorker(active_workers[0]);
  CHECK(context_id.is_valid());
  context.context_id = context_id.AsLowercaseString();
  context.tab_id = extension_misc::kUnknownTabId;
  context.window_id = extension_misc::kUnknownWindowId;
  // TODO(devlin): Add extension_misc::kUnknownFrameId and use it here?
  context.frame_id = -1;
  context.incognito = browser_context()->IsOffTheRecord();
  return context;
}

std::vector<api::runtime::ExtensionContext>
RuntimeGetContextsFunction::GetFrameContexts() {
  ProcessManager* const process_manager =
      ProcessManager::Get(browser_context());
  CHECK(process_manager);

  auto get_context_type = [](content::WebContents* web_contents) {
    mojom::ViewType view_type = GetViewType(web_contents);
    switch (view_type) {
      // These should never be reached for extensions capable of calling this
      // method.
      case mojom::ViewType::kInvalid:
      case mojom::ViewType::kAppWindow:
      case mojom::ViewType::kBackgroundContents:
      case mojom::ViewType::kComponent:
      case mojom::ViewType::kExtensionBackgroundPage:
        DUMP_WILL_BE_NOTREACHED();
        break;

      case mojom::ViewType::kExtensionPopup:
        return api::runtime::ContextType::kPopup;
      case mojom::ViewType::kTabContents:
        return api::runtime::ContextType::kTab;
      case mojom::ViewType::kOffscreenDocument:
        return api::runtime::ContextType::kOffscreenDocument;
      case mojom::ViewType::kExtensionSidePanel:
        return api::runtime::ContextType::kSidePanel;
      case mojom::ViewType::kDeveloperTools:
        return api::runtime::ContextType::kDeveloperTools;

      case mojom::ViewType::kExtensionGuest:
        // Skip these view types for now.
        break;

        // Adding a new mojom::ViewType? Consider whether it should be exposed
        // to extension developers via `chrome.runtime.getContexts()` and, if
        // so, add a new entry in the runtime API.
    }

    return api::runtime::ContextType::kNone;
  };

  ProcessManager::FrameSet frames =
      process_manager->GetRenderFrameHostsForExtension(extension()->id());
  std::vector<api::runtime::ExtensionContext> results;

  for (content::RenderFrameHost* host : frames) {
    content::WebContents* web_contents =
        content::WebContents::FromRenderFrameHost(host);
    CHECK(web_contents);

    auto context_type = get_context_type(web_contents);
    if (context_type == api::runtime::ContextType::kNone) {
      // Skip unsupported contexts.
      continue;
    }

    api::runtime::ExtensionContext context;
    context.context_type = context_type;
    context.context_id =
        ExtensionApiFrameIdMap::GetContextId(host).AsLowercaseString();
    context.tab_id = GetTabId(*web_contents);
    context.frame_id = GetFrameId(*host);
    context.window_id = GetWindowId(*web_contents);
    context.document_id =
        ExtensionApiFrameIdMap::GetDocumentId(host).ToString();
    context.document_url = host->GetLastCommittedURL().spec();
    context.document_origin = host->GetLastCommittedOrigin().Serialize();
    context.incognito = host->GetBrowserContext()->IsOffTheRecord();

    results.push_back(std::move(context));
  }

  return results;
}

int RuntimeGetContextsFunction::GetTabId(content::WebContents& web_contents) {
  mojom::ViewType view_type = extensions::GetViewType(&web_contents);

  if (view_type == extensions::mojom::ViewType::kDeveloperTools) {
    return -1;
  }

  return sessions::SessionTabHelper::IdForTab(&web_contents).id();
}

int RuntimeGetContextsFunction::GetFrameId(content::RenderFrameHost& host) {
  mojom::ViewType view_type = extensions::GetViewType(&host);

  if (view_type == extensions::mojom::ViewType::kDeveloperTools) {
    return -1;
  }

  return ExtensionApiFrameIdMap::GetFrameId(&host);
}

int RuntimeGetContextsFunction::GetWindowId(
    content::WebContents& web_contents) {
  mojom::ViewType view_type = extensions::GetViewType(&web_contents);

  if (view_type != extensions::mojom::ViewType::kDeveloperTools) {
    return sessions::SessionTabHelper::IdForWindowContainingTab(&web_contents)
        .id();
  }

  // For developer tools, ask the embedder for the window ID.
  std::unique_ptr<RuntimeAPIDelegate> delegate =
      ExtensionsBrowserClient::Get()->CreateRuntimeAPIDelegate(
          browser_context());
  return delegate->GetDeveloperToolsWindowId(&web_contents);
}

}  // namespace extensions