// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/gpu/gpu_process_host.h"

#include <stddef.h>

#include <algorithm>
#include <array>
#include <list>
#include <memory>
#include <utility>

#include "ash/constants/ash_switches.h"
#include "arkweb/build/features/features.h"
#include "arkweb/chromium_ext/base/process/process_handle_posix_ex.h"
#include "arkweb/chromium_ext/content/browser/renderer_host/arkweb_render_process_host_impl_utils.h"
#if BUILDFLAG(ARKWEB_REPORT_SYS_EVENT)
#include "arkweb/chromium_ext/third_party/crashpad/crashpad/util/linux/crashpad_dfx.h"
#endif
#include "base/base64.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_pump_type.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "cc/base/switches.h"
#include "components/discardable_memory/service/discardable_shared_memory_manager.h"
#include "components/tracing/common/tracing_switches.h"
#include "components/viz/common/features.h"
#include "components/viz/common/switches.h"
#include "components/viz/host/persistent_cache_sandboxed_file_factory.h"
#include "content/browser/browser_child_process_host_impl.h"
#include "content/browser/child_process_host_impl.h"
#include "content/browser/child_process_launcher.h"
#include "content/browser/compositor/image_transport_factory.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/gpu/gpu_data_manager_impl.h"
#include "content/browser/gpu/gpu_disk_cache_factory.h"
#include "content/browser/gpu/gpu_main_thread_factory.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/service_worker/service_worker_host.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/worker_host/dedicated_worker_host.h"
#include "content/browser/worker_host/dedicated_worker_service_impl.h"
#include "content/browser/worker_host/shared_worker_host.h"
#include "content/browser/worker_host/shared_worker_service_impl.h"
#include "content/common/child_process.mojom.h"
#include "content/common/features.h"
#include "content/common/in_process_child_thread_params.h"
#include "content/public/browser/browser_child_process_host.h"
#include "content/public/browser/browser_main_runner.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/gpu_utils.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/sandboxed_process_launcher_delegate.h"
#include "content/public/common/zygote/zygote_buildflags.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "gpu/config/gpu_driver_bug_list.h"
#include "gpu/config/gpu_driver_bug_workaround_type.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/config/gpu_preferences.h"
#include "gpu/config/gpu_switches.h"
#include "gpu/ipc/common/gpu_client_ids.h"
#include "gpu/ipc/common/result_codes.h"
#include "gpu/ipc/host/gpu_disk_cache.h"
#include "media/base/media_switches.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/network_isolation_key.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"
#include "sandbox/policy/sandbox_type.h"
#include "sandbox/policy/switches.h"
#include "services/webnn/buildflags.h"
#include "services/webnn/webnn_switches.h"
#include "skia/buildflags.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"
#include "ui/display/display_switches.h"
#include "ui/gfx/font_render_params.h"
#include "ui/gfx/switches.h"
#include "ui/gl/gl_features.h"
#include "ui/gl/gl_switches.h"
#include "ui/latency/latency_info.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/application_status_listener.h"
#else
#include "components/metrics/stability_metrics_helper.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "base/win/access_token.h"
#include "base/win/security_descriptor.h"
#include "base/win/win_util.h"
#include "components/app_launch_prefetch/app_launch_prefetch.h"
#include "sandbox/policy/win/sandbox_win.h"
#include "sandbox/win/src/sandbox_policy.h"
#include "sandbox/win/src/window.h"
#include "ui/gfx/win/rendering_window_manager.h"
#endif

#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/gpu_platform_support_host.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/ozone_switches.h"
#endif

#if BUILDFLAG(USE_ZYGOTE)
#include "content/common/zygote/zygote_handle_impl_linux.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "content/browser/gpu/browser_child_process_backgrounded_bridge.h"
#include "content/browser/gpu/ca_transaction_gpu_coordinator.h"
#endif

#if BUILDFLAG(ARKWEB_OOP_GPU_PROCESS)
#include "content/browser/renderer_host/render_process_host_impl.h"
#endif

#if BUILDFLAG(ARKWEB_PERFORMANCE_SCHEDULING)
#include "third_party/ohos_ndk/includes/ohos_adapter/res_sched_client_adapter.h"
#endif

namespace content {

base::subtle::Atomic32 GpuProcessHost::gpu_crash_count_ = 0;
bool GpuProcessHost::crashed_before_ = false;
int GpuProcessHost::recent_crash_count_ = 0;
gpu::GpuMode GpuProcessHost::last_crash_mode_ = gpu::GpuMode::UNKNOWN;
// RESULT_CODE_HUNG is expected to be the same in both
// content/public/common/result_codes.h and gpu/ipc/common/result_codes.h
static_assert(RESULT_CODE_HUNG == static_cast<int>(gpu::RESULT_CODE_HUNG),
              "Please use the same enum value in both header files.");

namespace {

// UMA histogram names.
constexpr char kFallbackEventCause[] = "GPU.FallbackEventCause";
constexpr char kProcessLifetimeEventsHardwareAccelerated[] =
    "GPU.ProcessLifetimeEvents.HardwareAccelerated";
constexpr char kProcessLifetimeEventsSwiftShader[] =
    "GPU.ProcessLifetimeEvents.SwiftShader";

// Returns the UMA histogram name for the given GPU mode.
const char* GetProcessLifetimeUmaName(gpu::GpuMode gpu_mode) {
  switch (gpu_mode) {
    // TODO(rivr): Add separate histograms for the different hardware modes.
    case gpu::GpuMode::UNKNOWN:
      NOTREACHED();
    case gpu::GpuMode::HARDWARE_GL:
    case gpu::GpuMode::HARDWARE_GRAPHITE:
    case gpu::GpuMode::HARDWARE_VULKAN:
      return kProcessLifetimeEventsHardwareAccelerated;
    case gpu::GpuMode::SOFTWARE_GL:
      // All software modes currently share the SwiftShader metric because we
      // cant differentiate different software backends at this level (and
      // probably don't want to).
      return kProcessLifetimeEventsSwiftShader;
    case gpu::GpuMode::DISPLAY_COMPOSITOR:
      return nullptr;
  }
}

// Forgive one GPU process crash after this many minutes.
// This value should not be too small because then Chrome could end up in an
// endless loop where it hangs and gets killed by GPU watchdog and hangs again.
constexpr int kForgiveGpuCrashMinutes = 5;

// Forgive one GPU process crash, when the GPU process is launched to run only
// the display compositor, after this many minutes.
constexpr int kForgiveDisplayCompositorCrashMinutes = 10;

int GetForgiveMinutes(gpu::GpuMode gpu_mode) {
  return gpu_mode == gpu::GpuMode::DISPLAY_COMPOSITOR
             ? kForgiveDisplayCompositorCrashMinutes
             : kForgiveGpuCrashMinutes;
}

// This matches base::TerminationStatus.
// These values are persisted to logs. Entries (except MAX_ENUM) should not be
// renumbered and numeric values should never be reused. Should also avoid
// OS-defines in this enum to keep the values consistent on all platforms.
enum class GpuTerminationStatus {
  NORMAL_TERMINATION = 0,
  ABNORMAL_TERMINATION = 1,
  PROCESS_WAS_KILLED = 2,
  PROCESS_CRASHED = 3,
  STILL_RUNNING = 4,
  PROCESS_WAS_KILLED_BY_OOM = 5,
  OOM_PROTECTED = 6,
  LAUNCH_FAILED = 7,
  OOM = 8,
  MAX_ENUM = 9,
};

GpuTerminationStatus ConvertToGpuTerminationStatus(
    base::TerminationStatus status) {
  switch (status) {
    case base::TERMINATION_STATUS_NORMAL_TERMINATION:
      return GpuTerminationStatus::NORMAL_TERMINATION;
    case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
      return GpuTerminationStatus::ABNORMAL_TERMINATION;
    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
      return GpuTerminationStatus::PROCESS_WAS_KILLED;
    case base::TERMINATION_STATUS_PROCESS_CRASHED:
#if BUILDFLAG(IS_WIN)
    // Treat integrity failure as a crash on Windows.
    case base::TERMINATION_STATUS_INTEGRITY_FAILURE:
#endif
      return GpuTerminationStatus::PROCESS_CRASHED;
    case base::TERMINATION_STATUS_STILL_RUNNING:
      return GpuTerminationStatus::STILL_RUNNING;
#if BUILDFLAG(IS_CHROMEOS)
    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
      return GpuTerminationStatus::PROCESS_WAS_KILLED_BY_OOM;
#endif
#if BUILDFLAG(IS_ANDROID)
    case base::TERMINATION_STATUS_OOM_PROTECTED:
      return GpuTerminationStatus::OOM_PROTECTED;
#endif
    case base::TERMINATION_STATUS_LAUNCH_FAILED:
      return GpuTerminationStatus::LAUNCH_FAILED;
    case base::TERMINATION_STATUS_OOM:
      return GpuTerminationStatus::OOM;
    case base::TERMINATION_STATUS_EVICTED_FOR_MEMORY:
      return GpuTerminationStatus::OOM;
    case base::TERMINATION_STATUS_MAX_ENUM:
      NOTREACHED();
      // Do not add default.
  }
  NOTREACHED();
}

// Command-line switches to propagate to the GPU process.
static const char* const kSwitchNames[] = {
    sandbox::policy::switches::kDisableSeccompFilterSandbox,
    sandbox::policy::switches::kGpuSandboxAllowSysVShm,
    sandbox::policy::switches::kGpuSandboxFailuresFatal,
    sandbox::policy::switches::kDisableGpuSandbox,
    sandbox::policy::switches::kDisableLandlockSandbox,
    sandbox::policy::switches::kNoSandbox,
#if BUILDFLAG(IS_WIN)
    sandbox::policy::switches::kAllowThirdPartyModules,
#endif
#if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS)
    switches::kDisableDevShmUsage,
#endif
#if BUILDFLAG(IS_WIN)
    switches::kDisableHighResTimer,
    switches::kRaiseTimerFrequency,
    switches::kUseRedistributableDirectML,
#endif  // BUILDFLAG(IS_WIN)
#if BUILDFLAG(ARKWEB_OOP_GPU_PROCESS)
    switches::kOhosEnableDrDc,
#endif
#if BUILDFLAG(ARKWEB_VULKAN)
    switches::kOhosEnableVulkan,
#endif
    switches::kBackgroundThreadPoolFieldTrial,
    switches::kEnableANGLEFeatures,
    switches::kDelegatedInkRenderer,
    switches::kDisableANGLEFeatures,
    switches::kDisableBreakpad,
    switches::kDisableGpuRasterization,
    switches::kDisableGLExtensions,
    switches::kDisableMipmapGeneration,
    switches::kDisableShaderNameHashing,
    switches::kDisableSkiaRuntimeOpts,
    switches::kDRMVirtualConnectorIsExternal,
    switches::kDumpCompositorFrame,
    switches::kEnableGpuMainTimeKeeperMetrics,
    switches::kEnableGpuRasterization,
    switches::kEnableSkiaGraphite,
    switches::kEnableSkiaGraphitePrecompilation,
    switches::kDoubleBufferCompositing,
    switches::kHeadless,
    switches::kEnableLowEndDeviceMode,
    switches::kDisableSkiaGraphite,
    switches::kDisableSkiaGraphitePrecompilation,
    switches::kDisableLowEndDeviceMode,
    switches::kProfilingAtStart,
    switches::kProfilingFile,
    switches::kProfilingFlush,
    switches::kRunAllCompositorStagesBeforeDraw,
    switches::kSkiaFontCacheLimitMb,
    switches::kSkiaGraphiteBackend,
    switches::kSkiaResourceCacheLimitMb,
    switches::kTestGLLib,
    switches::kUseAdapterLuid,
    switches::kUseFakeMjpegDecodeAccelerator,
    switches::kUseGpuInTests,
    switches::kWebViewDrawFunctorUsesVulkan,
    switches::kSuppressPerformanceLogs,
#if BUILDFLAG(IS_MAC)
    sandbox::policy::switches::kEnableSandboxLogging,
    sandbox::policy::switches::kDisableMetalShaderCache,
    switches::kShowMacOverlayBorders,
#endif
#if BUILDFLAG(IS_OZONE)
    switches::kOzonePlatform,
    switches::kDisableExplicitDmaFences,
    switches::kOzoneDumpFile,
    switches::kEnableNativeGpuMemoryBuffers,
    switches::kRenderNodeOverride,
#endif
#if BUILDFLAG(IS_LINUX)
    switches::kX11Display,
    switches::kNoXshm,
#endif
    switches::kGpuBlocklistTestGroup,
    switches::kGpuDriverBugListTestGroup,
    switches::kGpuWatchdogTimeoutSeconds,
    switches::kUseCmdDecoder,
    switches::kForceVideoOverlays,
    switches::kSkiaGraphiteBackend,
#if BUILDFLAG(IS_ANDROID)
    switches::kDisableAdpf,
#endif
#if BUILDFLAG(IS_CHROMEOS)
    // TODO(crbug.com/371609830): Remove reven switch on experiment end.
    ash::switches::kRevenBranding,
    switches::kSchedulerBoostUrgent,
#endif
#if BUILDFLAG(USE_V4L2_CODEC)
    switches::kHardwareVideoDecodeFrameRate,
#endif
};

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class GPUFallbackEventCauseType {
  kFailureToInit = 0,
  kCrashLimit = 1,
  kMaxValue = kCrashLimit,
};

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum GPUProcessLifetimeEvent {
  LAUNCHED = 0,
  // When the GPU process crashes the (DIED_FIRST_TIME + recent_crash_count - 1)
  // bucket in the appropriate UMA histogram will be incremented. The first
  // crash will be DIED_FIRST_TIME, the second DIED_FIRST_TIME+1, etc.
  DIED_FIRST_TIME = 1,
  GPU_PROCESS_LIFETIME_EVENT_MAX = 100,
};

// Indexed by GpuProcessKind. There is one of each kind maximum. This array may
// only be accessed from the UI thread.
std::array<GpuProcessHost*, GPU_PROCESS_KIND_COUNT> g_gpu_process_hosts;

static void RunCallbackOnUI(
    GpuProcessKind kind,
    bool force_create,
    base::OnceCallback<void(GpuProcessHost*)> callback) {
  // |GpuProcessHost::Get| asserts that we are on the UI thread.
  GpuProcessHost* host = GpuProcessHost::Get(kind, force_create);
  std::move(callback).Run(host);
}

void OnGpuProcessHostDestroyedOnUI(int host_id, const std::string& message) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  GpuDataManagerImpl::GetInstance()->AddLogMessage(logging::LOGGING_ERROR,
                                                   "GpuProcessHost", message);
#if BUILDFLAG(IS_OZONE)
  ui::OzonePlatform::GetInstance()
      ->GetGpuPlatformSupportHost()
      ->OnChannelDestroyed(host_id);
#endif
}

// NOTE: changes to this class need to be reviewed by the security team.
class GpuSandboxedProcessLauncherDelegate
    : public SandboxedProcessLauncherDelegate {
 public:
  explicit GpuSandboxedProcessLauncherDelegate(
      const base::CommandLine& cmd_line)
      : cmd_line_(cmd_line) {}

  ~GpuSandboxedProcessLauncherDelegate() override = default;

#if BUILDFLAG(IS_WIN)
  bool DisableDefaultPolicy() override { return true; }

  std::string GetSandboxTag() override {
    return sandbox::policy::SandboxWin::GetSandboxTagForDelegate(
        "gpu", GetSandboxType());
  }

  // For the GPU process we gotten as far as USER_LIMITED. The next level
  // which is USER_RESTRICTED breaks both the DirectX backend and the OpenGL
  // backend. Note that the GPU process is connected to the interactive
  // desktop.
  bool InitializeConfig(sandbox::TargetConfig* config) override {
    DCHECK(!config->IsConfigured());

    sandbox::ResultCode result = config->SetTokenLevel(
        sandbox::USER_RESTRICTED_SAME_ACCESS, sandbox::USER_LIMITED);
    if (result != sandbox::SBOX_ALL_OK) {
      return false;
    }

    // UI restrictions break when we access Windows from outside our job.
    // However, we don't want a proxy window in this process because it can
    // introduce deadlocks where the renderer blocks on the gpu, which in
    // turn blocks on the browser UI thread. So, instead we forgo a window
    // message pump entirely and just add job restrictions to prevent child
    // processes.
    result = sandbox::policy::SandboxWin::SetJobLevel(
        sandbox::mojom::Sandbox::kGpu, sandbox::JobLevel::kLimitedUser,
        JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | JOB_OBJECT_UILIMIT_DESKTOP |
            JOB_OBJECT_UILIMIT_EXITWINDOWS | JOB_OBJECT_UILIMIT_DISPLAYSETTINGS,
        config);
    if (result != sandbox::SBOX_ALL_OK) {
      return false;
    }

    // Check if we are running on the winlogon desktop and set a delayed
    // integrity in this case. This is needed because a low integrity gpu
    // process will not be allowed to access the winlogon desktop (gpu process
    // integrity has to be at least medium in order to be able to access the
    // winlogon desktop normally). So instead, let the gpu process start with
    // the normal integrity and delay the switch to low integrity until after
    // the gpu process has started and has access to the desktop.
    if (ShouldSetDelayedIntegrity()) {
      config->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
    } else {
      result = config->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
      if (result != sandbox::SBOX_ALL_OK)
        return false;
    }

    // Block this DLL even if it is not loaded by the browser process.
    config->AddDllToUnload(L"cmsetac.dll");

    return true;
  }
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(USE_ZYGOTE)
  ZygoteCommunication* GetZygote() override {
    if (sandbox::policy::IsUnsandboxedSandboxType(GetSandboxType()))
      return nullptr;

    // The GPU process needs a specialized sandbox, so fork from the unsandboxed
    // zygote and then apply the actual sandboxes in the forked process.
    return GetUnsandboxedZygote();
  }
#endif  // BUILDFLAG(USE_ZYGOTE)

  sandbox::mojom::Sandbox GetSandboxType() override {
    if (cmd_line_.HasSwitch(sandbox::policy::switches::kDisableGpuSandbox)) {
      DVLOG(1) << "GPU sandbox is disabled";
      return sandbox::mojom::Sandbox::kNoSandbox;
    }
    return sandbox::mojom::Sandbox::kGpu;
  }

 private:
#if BUILDFLAG(IS_WIN)
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class ProcessIntegrityResult{
      kLowIl = 0,
      kOpenGlMediumIl = 1,
      kDesktopAccessMediumIl = 2,
      kMaxValue = kDesktopAccessMediumIl,
  };

  bool CanLowIntegrityAccessDesktop() {
    // Access required for UI thread to initialize (when user32.dll loads
    // without win32k lockdown).
    DWORD desired_access = DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS;

    // Desktop is inherited by child process unless overridden, e.g. by sandbox.
    HDESK hdesk = ::GetThreadDesktop(GetCurrentThreadId());
    std::optional<base::win::SecurityDescriptor> sd =
        base::win::SecurityDescriptor::FromHandle(
            hdesk, base::win::SecurityObjectType::kDesktop,
            OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
                DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION);
    if (!sd) {
      return false;
    }

    std::optional<base::win::AccessToken> token =
        base::win::AccessToken::FromCurrentProcess(/*impersonation=*/true,
                                                   TOKEN_ADJUST_DEFAULT);
    if (!token) {
      return false;
    }

    if (!token->SetIntegrityLevel(SECURITY_MANDATORY_LOW_RID)) {
      return false;
    }

    std::optional<base::win::AccessCheckResult> result = sd->AccessCheck(
        *token, desired_access, base::win::SecurityObjectType::kDesktop);
    return result && result->access_status;
  }

  bool ShouldSetDelayedIntegrity() {
    // Desktop access is needed to load user32.dll, we can lower token in child
    // process after that's done.
    if (CanLowIntegrityAccessDesktop()) {
      return false;
    }
    return true;
  }
#endif

  base::CommandLine cmd_line_;
};

void BindDiscardableMemoryReceiverOnIO(
    mojo::PendingReceiver<
        discardable_memory::mojom::DiscardableSharedMemoryManager> receiver,
    discardable_memory::DiscardableSharedMemoryManager* manager) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  manager->Bind(std::move(receiver));
}

void BindDiscardableMemoryReceiverOnUI(
    mojo::PendingReceiver<
        discardable_memory::mojom::DiscardableSharedMemoryManager> receiver) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          &BindDiscardableMemoryReceiverOnIO, std::move(receiver),
          discardable_memory::DiscardableSharedMemoryManager::Get()));
}

// Initialize PersistentCacheSandboxedFileFactory instance.
// TODO(crbug.com/399642827): Consider moving this to
// src/content/browser/browser_main_loop.cc once the persistent cache is used
// for all cache types.
void InitGpuPersistentCacheFileFactoryOnce() {
#if BUILDFLAG(SKIA_USE_DAWN)
  if (features::kSkiaGraphiteDawnUsePersistentCache.Get() &&
      !viz::PersistentCacheSandboxedFileFactory::GetInstance()) {
    base::FilePath cache_root_dir =
        GetContentClient()->browser()->GetGPUPersistentCacheDirectory();
    if (cache_root_dir.empty()) {
      // GetGPUPersistentCacheDirectory() can return empty string in tests.
      // Disable caching in this case since PersistentCacheSandboxedFileFactory
      // doesn't support relative paths.
      return;
    }
    viz::PersistentCacheSandboxedFileFactory::CreateInstance(cache_root_dir);
  }
#endif
}

}  // anonymous namespace

// static
bool GpuProcessHost::ValidateHost(GpuProcessHost* host) {
  // The Gpu process is invalid if it's not using SwiftShader, the card is
  // blocklisted, and we can kill it and start over.
  static bool is_single_process =
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kSingleProcess);
  static bool in_process_GPU =
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kInProcessGPU);

  if (is_single_process || in_process_GPU || host->valid_) {
    return true;
  }

  host->ForceShutdown();
  return false;
}

// static
GpuProcessHost* GpuProcessHost::Get(GpuProcessKind kind, bool force_create) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Do not launch the unsandboxed GPU info collection process if GPU is
  // disabled
  if (kind == GPU_PROCESS_KIND_INFO_COLLECTION) {
    auto* command_line = base::CommandLine::ForCurrentProcess();
    if (command_line->HasSwitch(switches::kDisableGpu) ||
        command_line->HasSwitch(switches::kSingleProcess) ||
        command_line->HasSwitch(switches::kInProcessGPU))
      return nullptr;
  }

  if (g_gpu_process_hosts[kind] && ValidateHost(g_gpu_process_hosts[kind]))
    return g_gpu_process_hosts[kind];

  if (!force_create)
    return nullptr;

  // Do not create a new process if browser is shutting down.
  if (BrowserMainRunner::ExitedMainMessageLoop()) {
    DLOG(ERROR) << "BrowserMainRunner::ExitedMainMessageLoop()";
    return nullptr;
  }

  if (kind != GPU_PROCESS_KIND_INFO_COLLECTION) {
    InitGpuPersistentCacheFileFactoryOnce();
  }

  static int last_host_id = 0;
  int host_id;
  host_id = ++last_host_id;

  GpuProcessHost* host = new GpuProcessHost(host_id, kind);
  if (host->Init())
    return host;

  // TODO(sievers): Revisit this behavior. It's not really a crash, but we also
  // want the fallback-to-sw behavior if we cannot initialize the GPU.
  LOG(ERROR) << "GPU process failed to initialize.";
  host->RecordProcessCrash();

  delete host;
  return nullptr;
}

// static
void GpuProcessHost::GetHasGpuProcess(base::OnceCallback<void(bool)> callback) {
  if (!GetUIThreadTaskRunner({})->BelongsToCurrentThread()) {
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&GpuProcessHost::GetHasGpuProcess, std::move(callback)));
    return;
  }
  bool has_gpu = false;
  for (size_t i = 0; i < std::size(g_gpu_process_hosts); ++i) {
    GpuProcessHost* host = g_gpu_process_hosts[i];
    if (host && ValidateHost(host)) {
      has_gpu = true;
      break;
    }
  }
  std::move(callback).Run(has_gpu);
}

// static
void GpuProcessHost::CallOnUI(
    const base::Location& location,
    GpuProcessKind kind,
    bool force_create,
    base::OnceCallback<void(GpuProcessHost*)> callback) {
#if !BUILDFLAG(IS_WIN)
  DCHECK_NE(kind, GPU_PROCESS_KIND_INFO_COLLECTION);
#endif
  GetUIThreadTaskRunner({})->PostTask(
      location, base::BindOnce(&RunCallbackOnUI, kind, force_create,
                               std::move(callback)));
}

void GpuProcessHost::BindInterface(
    const std::string& interface_name,
    mojo::ScopedMessagePipeHandle interface_pipe) {
  if (interface_name ==
      discardable_memory::mojom::DiscardableSharedMemoryManager::Name_) {
    BindDiscardableMemoryReceiver(
        mojo::PendingReceiver<
            discardable_memory::mojom::DiscardableSharedMemoryManager>(
            std::move(interface_pipe)));
    return;
  }
  process_->child_process()->BindReceiver(
      mojo::GenericPendingReceiver(interface_name, std::move(interface_pipe)));
}

#if BUILDFLAG(IS_OZONE)
void GpuProcessHost::TerminateGpuProcess(const std::string& message) {
  // At the moment, this path is only used by Ozone/Wayland. Once others start
  // to use this, start to distinguish the origin of termination. By default,
  // it's unknown.
  termination_origin_ = GpuTerminationOrigin::kOzoneWaylandProxy;
  process_->TerminateOnBadMessageReceived(message);
}
#endif  // BUILDFLAG(IS_OZONE)

// static
GpuProcessHost* GpuProcessHost::FromID(int host_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  for (int i = 0; i < GPU_PROCESS_KIND_COUNT; ++i) {
    GpuProcessHost* host = g_gpu_process_hosts[i];
    if (host && host->host_id_ == host_id && ValidateHost(host))
      return host;
  }

  return nullptr;
}

// static
int GpuProcessHost::GetGpuCrashCount() {
  return static_cast<int>(base::subtle::NoBarrier_Load(&gpu_crash_count_));
}

// static
void GpuProcessHost::IncrementCrashCount(gpu::GpuMode gpu_mode) {
  int forgive_minutes = GetForgiveMinutes(gpu_mode);
  DCHECK_GT(forgive_minutes, 0);

  // Last time the process crashed.
  static base::TimeTicks last_crash_time;

  base::TimeTicks current_time = base::TimeTicks::Now();
  if (gpu_mode != last_crash_mode_) {
    // Reset the crash count when the GPU starts crashing in a different mode.
    recent_crash_count_ = 0;
  } else if (crashed_before_) {
    // Remove one crash per |forgive_minutes| from the crash count, so
    // occasional crashes won't add up and eventually prevent using the GPU
    // process.
    int minutes_delta = (current_time - last_crash_time).InMinutes();
    int crashes_to_forgive = minutes_delta / forgive_minutes;
    recent_crash_count_ = std::max(0, recent_crash_count_ - crashes_to_forgive);
  }
  recent_crash_count_ =
      std::min(recent_crash_count_ + 1,
               static_cast<int>(GPU_PROCESS_LIFETIME_EVENT_MAX) - 1);

  crashed_before_ = true;
  last_crash_mode_ = gpu_mode;
  last_crash_time = current_time;
}

GpuProcessHost::GpuProcessHost(int host_id, GpuProcessKind kind)
    : host_id_(host_id),
      valid_(true),
      in_process_(false),
      kind_(kind),
      process_launched_(false) {
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kSingleProcess) ||
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kInProcessGPU)) {
    in_process_ = true;
  }
#if !BUILDFLAG(IS_ANDROID)
  if (!in_process_ && kind != GPU_PROCESS_KIND_INFO_COLLECTION) {
    memory_pressure_listener_registration_ =
        std::make_unique<base::MemoryPressureListenerRegistration>(
            FROM_HERE, base::MemoryPressureListenerTag::kGpuProcessHost, this);
  }
#endif

  // If the 'single GPU process' policy ever changes, we still want to maintain
  // it for 'gpu thread' mode and only create one instance of host and thread.
  DCHECK(!in_process_ || g_gpu_process_hosts[kind] == nullptr);

  g_gpu_process_hosts[kind] = this;

  process_ =
      std::make_unique<BrowserChildProcessHostImpl>(PROCESS_TYPE_GPU, this);
}

GpuProcessHost::~GpuProcessHost() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (in_process_gpu_thread_)
    DCHECK(process_);

  if (!process_start_time_.is_null() &&
      kind_ != GPU_PROCESS_KIND_INFO_COLLECTION) {
    base::TimeDelta process_lifetime =
        base::TimeTicks::Now() - process_start_time_;

    // Use 2 weeks as the max bucket for GPU process lifetime since Chrome is
    // updated roughly once a week and it's unlikely to run for more than 2
    // weeks without restart. This histogram isn't using
    // UmaHistogramCustomTimes() because that records in milliseconds which are
    // too small when max is in weeks.
    constexpr int kLifetimeMax = 60 * 60 * 24 * 14;
    base::UmaHistogramCustomCounts("GPU.ProcessLifetime2",
                                   process_lifetime.InSeconds(), 1,
                                   kLifetimeMax, 50);
  }

  SendOutstandingReplies();

#if BUILDFLAG(IS_MAC)
  if (ca_transaction_gpu_coordinator_) {
    ca_transaction_gpu_coordinator_->HostWillBeDestroyed();
    ca_transaction_gpu_coordinator_ = nullptr;
  }
#endif

#if BUILDFLAG(ARKWEB_PERFORMANCE_SCHEDULING)
  if (in_process_gpu_thread_)
    OHOS::NWeb::ResSchedClientAdapter::ReportKeyThread(
        OHOS::NWeb::ResSchedStatusAdapter::THREAD_DESTROYED,
        base::GetCurrentRealPid(), in_process_gpu_thread_->GetThreadRealId().raw(),
        OHOS::NWeb::ResSchedRoleAdapter::IMPORTANT_DISPLAY);
#endif

  // This is only called on the UI thread so no race against the constructor
  // for another GpuProcessHost.
  if (g_gpu_process_hosts[kind_] == this)
    g_gpu_process_hosts[kind_] = nullptr;

  bool block_offscreen_contexts = true;
  if (!in_process_ && process_launched_) {
    ChildProcessTerminationInfo info =
        process_->GetTerminationInfo(/*known_dead=*/false);
    std::string message;
    if (kind_ == GPU_PROCESS_KIND_SANDBOXED) {
      UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessTerminationStatus2",
                                ConvertToGpuTerminationStatus(info.status),
                                GpuTerminationStatus::MAX_ENUM);
      int exit_code = std::clamp(info.exit_code, 0, 100);
#if !BUILDFLAG(IS_ANDROID)
      if (info.status != base::TERMINATION_STATUS_NORMAL_TERMINATION &&
          info.status != base::TERMINATION_STATUS_STILL_RUNNING &&
          exit_code !=
              static_cast<int>(content::RESULT_CODE_GPU_DEAD_ON_ARRIVAL)) {
        // Add a sample to Stability.Counts2's GPU crash bucket.
        //
        // On Android Chrome, GPU crashes are logged via
        // ContentStabilityMetricsProvider::OnCrashDumpProcessed() and
        // StabilityMetricsHelper::IncreaseGpuCrashCount().
        metrics::StabilityMetricsHelper::RecordStabilityEvent(
            metrics::StabilityEventType::kGpuCrash);
      }
#endif  // !BUILDFLAG(IS_ANDROID)

      if (info.status == base::TERMINATION_STATUS_NORMAL_TERMINATION ||
          info.status == base::TERMINATION_STATUS_ABNORMAL_TERMINATION ||
          info.status == base::TERMINATION_STATUS_PROCESS_CRASHED) {
        // Windows always returns PROCESS_CRASHED on abnormal termination, as it
        // doesn't have a way to distinguish the two.
        base::UmaHistogramSparse("GPU.GPUProcessExitCode", exit_code);
      }

      message = "The GPU process ";
    } else {
      message = "The info collection GPU process ";
    }

    bool unexpected_exit = false;
    switch (info.status) {
      case base::TERMINATION_STATUS_NORMAL_TERMINATION:
        // Don't block offscreen contexts (and force page reload for webgl)
        // if this was an intentional shutdown or the OOM killer on Android
        // killed us while Chrome was in the background.
        block_offscreen_contexts = false;
        message += "exited normally. Everything is okay.";
        break;
      case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
        message +=
            "exited with code " + CrashExitCodeToString(info.exit_code) + ".";
        unexpected_exit = true;
        break;
      case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
        UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessTerminationOrigin",
                                  termination_origin_,
                                  GpuTerminationOrigin::kMax);
        message += "was killed by you! Why?";
        break;
      case base::TERMINATION_STATUS_PROCESS_CRASHED:
        message +=
            "crashed! Exit code: " + CrashExitCodeToString(info.exit_code) +
            ".";
        unexpected_exit = true;
        break;
      case base::TERMINATION_STATUS_STILL_RUNNING:
        message += "hasn't exited yet.";
        break;
#if BUILDFLAG(IS_CHROMEOS)
      case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
        message += "was killed due to out of memory.";
        unexpected_exit = true;
        break;
#endif  // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_ANDROID)
      case base::TERMINATION_STATUS_OOM_PROTECTED:
        message += "was protected from out of memory kill.";
        unexpected_exit = true;
        break;
#endif  // BUILDFLAG(IS_ANDROID)
      case base::TERMINATION_STATUS_LAUNCH_FAILED:
        message += "failed to start!";
        unexpected_exit = true;
        break;
      case base::TERMINATION_STATUS_OOM:
        message += "died due to out of memory.";
        unexpected_exit = true;
        break;
#if BUILDFLAG(IS_WIN)
      case base::TERMINATION_STATUS_INTEGRITY_FAILURE:
        message += "failed integrity checks.";
        unexpected_exit = true;
        break;
#endif
      case base::TERMINATION_STATUS_EVICTED_FOR_MEMORY:
        message += "evicted for memory.";
        unexpected_exit = true;
        break;
      case base::TERMINATION_STATUS_MAX_ENUM:
        NOTREACHED();
    }
    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kForceBrowserCrashOnGpuCrash)) {
      CHECK(!unexpected_exit)
          << "Force Chrome to crash due to unexpected GPU process crash";
    }
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&OnGpuProcessHostDestroyedOnUI, host_id_, message));
  }

  // If there are any remaining offscreen contexts at the point the GPU process
  // exits, assume something went wrong, and block their URLs from accessing
  // client 3D APIs without prompting.
  if (block_offscreen_contexts && gpu_host_)
    gpu_host_->BlockLiveOffscreenContexts();
}

bool GpuProcessHost::Init() {
  init_start_time_ = base::TimeTicks::Now();

  TRACE_EVENT_INSTANT0("gpu", "LaunchGpuProcess", TRACE_EVENT_SCOPE_THREAD);

  process_->GetHost()->CreateChannel();

  mode_ = GpuDataManagerImpl::GetInstance()->GetGpuMode();

  if (in_process_) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK(GetGpuMainThreadFactory());
    gpu::GpuPreferences gpu_preferences = GetGpuPreferencesFromCommandLine();
    GpuDataManagerImpl::GetInstance()->UpdateGpuPreferences(
        &gpu_preferences, GPU_PROCESS_KIND_SANDBOXED);
    in_process_gpu_thread_.reset(GetGpuMainThreadFactory()(
        InProcessChildThreadParams(
            base::SingleThreadTaskRunner::GetCurrentDefault(),
            process_->GetInProcessMojoInvitation(), GetIOThreadTaskRunner()),
        gpu_preferences));
    base::Thread::Options options;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
    // WGL needs to create its own window and pump messages on it.
    options.message_pump_type = base::MessagePumpType::UI;
#endif
    options.thread_type = base::ThreadType::kDisplayCritical;
    in_process_gpu_thread_->StartWithOptions(std::move(options));
#if BUILDFLAG(ARKWEB_PERFORMANCE_SCHEDULING)
    OHOS::NWeb::ResSchedClientAdapter::ReportKeyThread(
        OHOS::NWeb::ResSchedStatusAdapter::THREAD_CREATED,
        base::GetCurrentRealPid(), in_process_gpu_thread_->GetThreadRealId().raw(),
        OHOS::NWeb::ResSchedRoleAdapter::IMPORTANT_DISPLAY);
#endif
  } else if (!LaunchGpuProcess()) {
    return false;
  }

  mojo::PendingRemote<viz::mojom::VizMain> viz_main_pending_remote;
  process_->child_process()->BindServiceInterface(
      viz_main_pending_remote.InitWithNewPipeAndPassReceiver());
  viz::GpuHostImpl::InitParams params;
  params.restart_id = host_id_;
  params.disable_gpu_shader_disk_cache = !features::IsShaderDiskCacheEnabled(
      base::CommandLine::ForCurrentProcess());
  params.product = GetContentClient()->browser()->GetProduct();
  params.deadline_to_synchronize_surfaces =
      switches::GetDeadlineToSynchronizeSurfaces();
  params.main_thread_task_runner = GetUIThreadTaskRunner({});
  params.info_collection_gpu_process =
      kind_ == GPU_PROCESS_KIND_INFO_COLLECTION;
  params.gpu_service_running_in_process = in_process_;
  gpu_host_ = std::make_unique<viz::GpuHostImpl>(
      this, std::move(viz_main_pending_remote), std::move(params));

  if (in_process_) {
    // Fake a callback that the process is ready.
    OnProcessLaunched();
  }

#if BUILDFLAG(IS_MAC)
  ca_transaction_gpu_coordinator_ = CATransactionGPUCoordinator::Create(this);
#endif

#if BUILDFLAG(ARKWEB_OOP_GPU_PROCESS)
  if (gpu_crash_count_ != 0) {
    ArkwebRenderProcessHostImplUtils::Refresh();
  }
#endif

  return true;
}

void GpuProcessHost::OnProcessLaunched() {
  process_start_time_ = base::TimeTicks::Now();
  UMA_HISTOGRAM_TIMES("GPU.GPUProcessLaunchTime",
                      process_start_time_ - init_start_time_);
  DCHECK(gpu_host_);
  if (in_process_) {
    // Don't set |process_id_| as it is publicly available through process_id().
    gpu_host_->SetProcessId(base::GetCurrentProcId());
  } else {
    process_id_ = process_->GetProcess().Pid();
    DCHECK_NE(base::kNullProcessId, process_id_);
    gpu_host_->SetProcessId(process_id_);

#if BUILDFLAG(IS_MAC)
    if (base::FeatureList::IsEnabled(features::kAdjustGpuProcessPriority)) {
      browser_child_process_backgrounded_bridge_ =
          std::make_unique<BrowserChildProcessBackgroundedBridge>(
              process_.get());
    }
#endif
  }
}

#if BUILDFLAG(ARKWEB_REPORT_SYS_EVENT)
const std::string process_type = "gpu";
#endif

void GpuProcessHost::OnProcessLaunchFailed(int error_code) {
  LOG(ERROR) << "GPU process launch failed: error_code=" << error_code;
#if BUILDFLAG(ARKWEB_REPORT_SYS_EVENT)
  std::string error_msg = "gpu process launch failed, error code:" + std::to_string(error_code);
  crashpad::CrashpadDfx::ProcessCrashReport(process_type, "", "", error_msg);
#endif
  RecordProcessCrash();
}

void GpuProcessHost::OnProcessCrashed(int exit_code) {
  // Record crash before doing anything that could start a new GPU process.
  LOG(ERROR) << "GPU process exited unexpectedly: exit_code=" << exit_code;
#if BUILDFLAG(ARKWEB_REPORT_SYS_EVENT)
  std::string error_msg = "gpu process crashed, error code:" + std::to_string(exit_code);
  crashpad::CrashpadDfx::ProcessCrashReport(process_type, "", "", error_msg);
#endif
  RecordProcessCrash();
  gpu_host_->OnProcessCrashed();
  SendOutstandingReplies();
  GpuDataManagerImpl::GetInstance()->ProcessCrashed();
}

gpu::GPUInfo GpuProcessHost::GetGPUInfo() const {
  return GpuDataManagerImpl::GetInstance()->GetGPUInfo();
}

gpu::GpuFeatureInfo GpuProcessHost::GetGpuFeatureInfo() const {
  return GpuDataManagerImpl::GetInstance()->GetGpuFeatureInfo();
}

void GpuProcessHost::DidInitialize(
    const gpu::GPUInfo& gpu_info,
    const gpu::GpuFeatureInfo& gpu_feature_info,
    const std::optional<gpu::GPUInfo>& gpu_info_for_hardware_gpu,
    const std::optional<gpu::GpuFeatureInfo>& gpu_feature_info_for_hardware_gpu,
    const gfx::GpuExtraInfo& gpu_extra_info) {
  if (GetGpuCrashCount() > 0) {
    LOG(WARNING) << "Reinitialized the GPU process after a crash. The reported "
                    "initialization time was "
                 << gpu_info.initialization_time.InMilliseconds() << " ms";
  }
  if (kind_ != GPU_PROCESS_KIND_INFO_COLLECTION) {
    auto* gpu_data_manager = GpuDataManagerImpl::GetInstance();
    // Update GpuFeatureInfo first, because UpdateGpuInfo() will notify all
    // listeners.
    gpu_data_manager->UpdateGpuFeatureInfo(gpu_feature_info,
                                           gpu_feature_info_for_hardware_gpu);
    gpu_data_manager->UpdateGpuInfo(gpu_info, gpu_info_for_hardware_gpu);
    gpu_data_manager->UpdateGpuExtraInfo(gpu_extra_info);
  }

#if BUILDFLAG(IS_ANDROID)
  // Android may kill the GPU process to free memory, especially when the app
  // is the background, so Android cannot have a hard limit on GPU starts.
  // Reset crash count on Android when context creation succeeds, but only if no
  // fallback option is available.
  if (!GpuDataManagerImpl::GetInstance()->CanFallback())
    recent_crash_count_ = 0;
#endif
}

void GpuProcessHost::DidFailInitialize() {
  did_fail_initialize_ = true;
  if (kind_ == GPU_PROCESS_KIND_SANDBOXED) {
    base::UmaHistogramEnumeration(kFallbackEventCause,
                                  GPUFallbackEventCauseType::kFailureToInit);
    GpuDataManagerImpl::GetInstance()->FallBackToNextGpuMode();
  }
}

void GpuProcessHost::DidCreateContextSuccessfully() {
#if BUILDFLAG(IS_ANDROID)
  // Android may kill the GPU process to free memory, especially when the app
  // is the background, so Android cannot have a hard limit on GPU starts.
  // Reset crash count on Android when context creation succeeds, but only if no
  // fallback option is available.
  if (!GpuDataManagerImpl::GetInstance()->CanFallback())
    recent_crash_count_ = 0;
#endif
}

void GpuProcessHost::MaybeShutdownGpuProcess() {
  if (!in_process_ &&
      GetContentClient()->browser()->CanShutdownGpuProcessNowOnIOThread()) {
    delete this;
  }
}

void GpuProcessHost::DidUpdateGPUInfo(const gpu::GPUInfo& gpu_info) {
  GpuDataManagerImpl::GetInstance()->UpdateGpuInfo(gpu_info, std::nullopt);
}

#if BUILDFLAG(IS_WIN)
void GpuProcessHost::DidUpdateOverlayInfo(
    const gpu::OverlayInfo& overlay_info) {
  GpuDataManagerImpl::GetInstance()->UpdateOverlayInfo(overlay_info);
}

void GpuProcessHost::DidUpdateDXGIInfo(gfx::mojom::DXGIInfoPtr dxgi_info) {
  GpuDataManagerImpl::GetInstance()->UpdateDXGIInfo(std::move(dxgi_info));
}
#endif

std::string GpuProcessHost::GetIsolationKey(
    int32_t process_id,
    const blink::WebGPUExecutionContextToken& token) {
  if (token.Is<blink::DocumentToken>()) {
    // Return an empty isolation key if the frame host is gone. This could
    // happen if the frame is destroyed (or being destroyed) in between when we
    // are trying to get the isolation key.
    RenderFrameHostImpl* frame_host = RenderFrameHostImpl::FromDocumentToken(
        process_id, token.GetAs<blink::DocumentToken>());
    if (!frame_host) {
      return "";
    }

    auto isolation_key =
        frame_host->GetNetworkIsolationKey().ToCacheKeyString();
    return isolation_key ? *isolation_key : "";
  } else if (token.Is<blink::DedicatedWorkerToken>()) {
    // Return an empty isolation key if the process host or the worker host is
    // gone. This may happen if the worker is destroyed (or being destroyed) in
    // between when we are trying to get the isolation key.
    RenderProcessHost* render_process_host =
        RenderProcessHost::FromID(process_id);
    if (!render_process_host ||
        !render_process_host->IsInitializedAndNotDead()) {
      return "";
    }
    DedicatedWorkerHost* dedicated_worker_host =
        static_cast<StoragePartitionImpl*>(
            render_process_host->GetStoragePartition())
            ->GetDedicatedWorkerService()
            ->GetDedicatedWorkerHostFromToken(
                token.GetAs<blink::DedicatedWorkerToken>());
    if (!dedicated_worker_host) {
      return "";
    }

    auto isolation_key =
        dedicated_worker_host->GetNetworkIsolationKey().ToCacheKeyString();
    return isolation_key ? *isolation_key : "";
  } else if (token.Is<blink::SharedWorkerToken>()) {
    // Return an empty isolation key if the process host or the worker host is
    // gone. This may happen if the worker is destroyed (or being destroyed) in
    // between when we are trying to get the isolation key.
    RenderProcessHost* render_process_host =
        RenderProcessHost::FromID(process_id);
    if (!render_process_host ||
        !render_process_host->IsInitializedAndNotDead()) {
      return "";
    }
    auto* storage_partition = static_cast<StoragePartitionImpl*>(
        render_process_host->GetStoragePartition());
    auto* worker_service = static_cast<SharedWorkerServiceImpl*>(
        storage_partition->GetSharedWorkerService());
    SharedWorkerHost* shared_worker_host =
        worker_service->GetSharedWorkerHostFromToken(
            token.GetAs<blink::SharedWorkerToken>());
    if (!shared_worker_host) {
      return "";
    }

    auto isolation_key =
        shared_worker_host->GetNetworkIsolationKey().ToCacheKeyString();
    return isolation_key ? *isolation_key : "";
  } else if (token.Is<blink::ServiceWorkerToken>()) {
    // Return an empty isolation key if the process host or the worker host is
    // gone. This may happen if the worker is destroyed (or being destroyed) in
    // between when we are trying to get the isolation key.
    RenderProcessHost* render_process_host =
        RenderProcessHost::FromID(process_id);
    if (!render_process_host ||
        !render_process_host->IsInitializedAndNotDead()) {
      return "";
    }
    ServiceWorkerContextWrapper* service_worker_context =
        static_cast<StoragePartitionImpl*>(
            render_process_host->GetStoragePartition())
            ->GetServiceWorkerContext();
    if (!service_worker_context) {
      return "";
    }
    for (const auto& kv :
         service_worker_context->GetRunningServiceWorkerInfos()) {
      int64_t version_id = kv.first;
      ServiceWorkerVersion* version =
          service_worker_context->GetLiveVersion(version_id);
      if (!version) {
        continue;
      }
      ServiceWorkerHost* service_worker_host = version->worker_host();
      if (!service_worker_host) {
        continue;
      }
      if (service_worker_host->token() !=
          token.GetAs<blink::ServiceWorkerToken>()) {
        continue;
      }
      auto isolation_key =
          service_worker_host->GetNetworkIsolationKey().ToCacheKeyString();
      return isolation_key ? *isolation_key : "";
    }
    // Return an empty isolation key if there's no workers matching the token.
    // This may happen if a user has a service worker started locally for a web
    // app but now uses same origin for another web app bar which doesn't have a
    // service worker.
    return "";
  }

  NOTREACHED();
}

void GpuProcessHost::BlockDomainsFrom3DAPIs(const std::set<GURL>& urls,
                                            gpu::DomainGuilt guilt) {
  GpuDataManagerImpl::GetInstance()->BlockDomainsFrom3DAPIs(urls, guilt);
}

bool GpuProcessHost::GpuAccessAllowed() const {
  return GpuDataManagerImpl::GetInstance()->GpuAccessAllowed(nullptr);
}

void GpuProcessHost::DisableGpuCompositing() {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS)
  DLOG(ERROR) << "Can't disable GPU compositing";
#elif BUILDFLAG(IS_ARKWEB)
  LOG(ERROR) << "Can't disable GPU compositing";
#else
  // TODO(crbug.com/40565996): The switch from GPU to software compositing
  // should be handled here instead of by ImageTransportFactory.
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce([]() {
        if (auto* factory = ImageTransportFactory::GetInstance())
          factory->DisableGpuCompositing();
      }));
#endif
}

gpu::GpuDiskCacheFactory* GpuProcessHost::GetGpuDiskCacheFactory() {
  return GetGpuDiskCacheFactorySingleton();
}

void GpuProcessHost::RecordLogMessage(int32_t severity,
                                      const std::string& header,
                                      const std::string& message) {
  GpuDataManagerImpl::GetInstance()->AddLogMessage(severity, header, message);
}

void GpuProcessHost::BindDiscardableMemoryReceiver(
    mojo::PendingReceiver<
        discardable_memory::mojom::DiscardableSharedMemoryManager> receiver) {
    BindDiscardableMemoryReceiverOnUI(std::move(receiver));
}

GpuProcessKind GpuProcessHost::kind() {
  return kind_;
}

// Atomically shut down the GPU process with a normal termination status.
void GpuProcessHost::ForceShutdown() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // This is only called on the UI thread so no race against the constructor
  // for another GpuProcessHost.
  if (g_gpu_process_hosts[kind_] == this)
    g_gpu_process_hosts[kind_] = nullptr;

  process_->ForceShutdown();
}

void GpuProcessHost::DumpProcessStack() {
#if BUILDFLAG(IS_ANDROID)
  if (in_process_)
    return;
  process_->DumpProcessStack();
#endif
}

void GpuProcessHost::RunServiceImpl(mojo::GenericPendingReceiver receiver) {
  process_->child_process()->BindServiceInterface(std::move(receiver));
}

bool GpuProcessHost::LaunchGpuProcess() {
  const base::CommandLine& browser_command_line =
      *base::CommandLine::ForCurrentProcess();

  base::CommandLine::StringType gpu_launcher =
      browser_command_line.GetSwitchValueNative(switches::kGpuLauncher);

#if BUILDFLAG(IS_ANDROID)
  // crbug.com/447735. readlink("self/proc/exe") sometimes fails on Android
  // at startup with EACCES. As a workaround ignore this here, since the
  // executable name is actually not used or useful anyways.
  std::unique_ptr<base::CommandLine> cmd_line =
      std::make_unique<base::CommandLine>(base::CommandLine::NO_PROGRAM);
#else
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  int child_flags = gpu_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF
                                         : ChildProcessHost::CHILD_NORMAL;
#elif BUILDFLAG(IS_MAC)
  int child_flags =
      features::IsSwiftShaderAllowed(base::CommandLine::ForCurrentProcess())
          ? ChildProcessHost::CHILD_GPU
          : ChildProcessHost::CHILD_NORMAL;
#else
  int child_flags = ChildProcessHost::CHILD_NORMAL;
#endif

  base::FilePath exe_path = ChildProcessHost::GetChildPath(child_flags);
  if (exe_path.empty())
    return false;

  std::unique_ptr<base::CommandLine> cmd_line =
      std::make_unique<base::CommandLine>(exe_path);
#endif

  cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kGpuProcess);

#if BUILDFLAG(IS_WIN)
  if (kind_ == GPU_PROCESS_KIND_INFO_COLLECTION) {
    cmd_line->AppendArgNative(app_launch_prefetch::GetPrefetchSwitch(
        app_launch_prefetch::SubprocessType::kGPUInfo));
  } else {
    cmd_line->AppendArgNative(app_launch_prefetch::GetPrefetchSwitch(
        app_launch_prefetch::SubprocessType::kGPU));
  }
#endif  // BUILDFLAG(IS_WIN)

  if (kind_ == GPU_PROCESS_KIND_INFO_COLLECTION) {
    cmd_line->AppendSwitch(sandbox::policy::switches::kDisableGpuSandbox);
    cmd_line->AppendSwitchASCII(switches::kUseGL,
                                gl::kGLImplementationDisabledName);

    // Pass the current device info to the info-collection GPU process for
    // crash key logging.
    const gpu::GPUInfo::GPUDevice device_info = GetGPUInfo().active_gpu();
    cmd_line->AppendSwitchASCII(
        switches::kGpuVendorId,
        base::StringPrintf("%u", device_info.vendor_id));
    cmd_line->AppendSwitchASCII(
        switches::kGpuDeviceId,
        base::StringPrintf("%u", device_info.device_id));
#if BUILDFLAG(IS_WIN)
    cmd_line->AppendSwitchASCII(
        switches::kGpuSubSystemId,
        base::StringPrintf("%u", device_info.sub_sys_id));
    cmd_line->AppendSwitchASCII(switches::kGpuRevision,
                                base::StringPrintf("%u", device_info.revision));
#endif
    if (device_info.driver_version.length()) {
      cmd_line->AppendSwitchASCII(switches::kGpuDriverVersion,
                                  device_info.driver_version);
    }
  }

  // TODO(penghuang): Replace all GPU related switches with GpuPreferences.
  // https://crbug.com/590825
  // If you want a browser command-line switch passed to the GPU process
  // you need to add it to |kSwitchNames| at the beginning of this file.
  cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames);
  cmd_line->CopySwitchesFrom(browser_command_line,
                             switches::kGLSwitchesCopiedFromGpuProcessHost);
  cmd_line->CopySwitchesFrom(
      browser_command_line,
      switches::GetWebNNSwitchesCopiedFromGpuProcessHost());

  if (browser_command_line.HasSwitch(switches::kDisableFrameRateLimit))
    cmd_line->AppendSwitch(switches::kDisableGpuVsync);

  if (browser_command_line.HasSwitch(switches::kForceHighPerformanceGPU)) {
    cmd_line->AppendSwitch(gpu::GpuDriverBugWorkaroundTypeToString(
        gpu::FORCE_HIGH_PERFORMANCE_GPU));
  }

  std::vector<const char*> gpu_workarounds;
  gpu::GpuDriverBugList::AppendAllWorkarounds(&gpu_workarounds);
  cmd_line->CopySwitchesFrom(browser_command_line, gpu_workarounds);

  // Because AppendExtraCommandLineSwitches is called here, we should call
  // LaunchWithoutExtraCommandLineSwitches() instead of Launch for gpu process
  // launch below.
  GetContentClient()->browser()->AppendExtraCommandLineSwitches(
      cmd_line.get(), process_->GetData().id);

  // TODO(kylechar): The command line flags added here should be based on
  // |mode_|.
  GpuDataManagerImpl::GetInstance()->AppendGpuCommandLine(cmd_line.get(),
                                                          kind_);

  // If specified, prepend a launcher program to the command line.
  if (!gpu_launcher.empty())
    cmd_line->PrependWrapper(gpu_launcher);

  std::unique_ptr<GpuSandboxedProcessLauncherDelegate> delegate =
      std::make_unique<GpuSandboxedProcessLauncherDelegate>(*cmd_line);

  // Do not call process_->Launch() here.
  // AppendExtraCommandLineSwitches will be called again in process_->Launch(),
  // Call LaunchWithoutExtraCommandLineSwitches() so the command line switches
  // will not be appended twice.
  process_->LaunchWithoutExtraCommandLineSwitches(
      std::move(delegate), std::move(cmd_line),
      /*file_data=*/
      std::make_unique<ChildProcessLauncherFileData>());
  process_launched_ = true;

  if (kind_ == GPU_PROCESS_KIND_SANDBOXED) {
    auto* const histogram = GetProcessLifetimeUmaName(mode_);
    if (histogram) {
      base::UmaHistogramEnumeration(histogram, LAUNCHED,
                                    GPU_PROCESS_LIFETIME_EVENT_MAX);
    }
  }

  return true;
}

void GpuProcessHost::SendOutstandingReplies() {
  valid_ = false;

  if (gpu_host_)
    gpu_host_->SendOutstandingReplies();
}

int GpuProcessHost::GetFallbackCrashLimit() const {
#if BUILDFLAG(IS_ANDROID)
  // If there is fallback (so it doesn't crash the browser) and app is
  // foreground (meaning crash is less liekly to be due to android OS
  // killing the GPU process arbitrarily to free memory), then use the normal
  // limit.
  if (GpuDataManagerImpl::GetInstance()->CanFallback() &&
      base::android::ApplicationStatusListener::HasVisibleActivities()) {
    return 3;
  } else {
    // Otherwise use a larger maximum crash count limit here to account for
    // Android OS killing the GPU process arbitrarily and fallback may crash the
    // browser process.
    return 6;
  }
#elif BUILDFLAG(IS_CHROMEOS)
  // Chrome OS does not use software compositing and fallback crashes the
  // browser process. So use larger maximum crash count limit.
  return 6;
#elif BUILDFLAG(ARKWEB_OOP_GPU_PROCESS)
  return 6;
#else
  // Maximum number of times the GPU process can crash before we try something
  // different, like disabling hardware acceleration or all GL.
  return 3;
#endif
}

void GpuProcessHost::RecordProcessCrash() {
  // Ending only acts as a failure if the GPU process was actually started and
  // was intended for actual rendering (and not just checking caps or other
  // options).
  if (!process_launched_ || kind_ != GPU_PROCESS_KIND_SANDBOXED)
    return;

  // Keep track of the total number of GPU crashes.
  base::subtle::NoBarrier_AtomicIncrement(&gpu_crash_count_, 1);
  LOG(WARNING) << "The GPU process has crashed " << GetGpuCrashCount()
               << " time(s)";

  // It's possible GPU mode fallback has already happened. In this case, |mode_|
  // will still be the mode of the failed process.
  IncrementCrashCount(mode_);
  auto* const histogram = GetProcessLifetimeUmaName(mode_);
  if (histogram) {
    base::UmaHistogramExactLinear(
        histogram, DIED_FIRST_TIME + recent_crash_count_ - 1,
        static_cast<int>(GPU_PROCESS_LIFETIME_EVENT_MAX));
  }
  // GPU process initialization failed and fallback already happened.
  if (did_fail_initialize_)
    return;

  bool disable_crash_limit = base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kDisableGpuProcessCrashLimit);

  // GPU process crashed too many times, fallback on a different GPU process
  // mode.
  if (recent_crash_count_ >= GetFallbackCrashLimit() && !disable_crash_limit) {
    base::UmaHistogramEnumeration(kFallbackEventCause,
                                  GPUFallbackEventCauseType::kCrashLimit);
    GpuDataManagerImpl::GetInstance()->FallBackToNextGpuModeDueToCrash();
  }
}

viz::mojom::GpuService* GpuProcessHost::gpu_service() {
  DCHECK(gpu_host_);
  return gpu_host_->gpu_service();
}

#if BUILDFLAG(IS_WIN)
viz::mojom::InfoCollectionGpuService*
GpuProcessHost::info_collection_gpu_service() {
  DCHECK(gpu_host_);
  return gpu_host_->info_collection_gpu_service();
}
#endif

int GpuProcessHost::GetIDForTesting() const {
  return process_->GetData().id;
}

#if !BUILDFLAG(IS_ANDROID)
void GpuProcessHost::OnMemoryPressure(base::MemoryPressureLevel level) {
  gpu_host_->gpu_service()->OnMemoryPressure(level);
}
#endif

}  // namespace content