/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h"

#include <algorithm>
#include <limits>
#include <memory>
#include <utility>

#include "base/debug/crash_logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/system/sys_info.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/crash/core/common/crash_key.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/public/common/switches.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/binding_security.h"
#include "third_party/blink/renderer/bindings/core/v8/capture_source_location.h"
#include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
#include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
#include "third_party/blink/renderer/bindings/core/v8/rejected_promises.h"
#include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/shadow_realm_context.h"
#include "third_party/blink/renderer/bindings/core/v8/use_counter_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_context_snapshot.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_error_event.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_idle_task_runner.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_metrics.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_trusted_script.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_string_trustedscript.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h"
#include "third_party/blink/renderer/core/events/error_event.h"
#include "third_party/blink/renderer/core/execution_context/agent.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/inspector/main_thread_debugger.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/script/modulator.h"
#include "third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.h"
#include "third_party/blink/renderer/core/timing/time_clamper.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/core/workers/worklet_global_scope.h"
#include "third_party/blink/renderer/platform/bindings/active_script_wrappable_manager.h"
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/source_location.h"
#include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include "third_party/blink/renderer/platform/heap/thread_state_storage.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/common/features.h"
#include "third_party/blink/renderer/platform/scheduler/public/main_thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/reporting_disposition.h"
#include "third_party/blink/renderer/platform/wtf/sanitizers.h"
#include "third_party/blink/renderer/platform/wtf/stack_util.h"
#include "third_party/blink/renderer/platform/wtf/text/strcat.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "tools/v8_context_snapshot/buildflags.h"
#include "v8/include/v8-profiler.h"
#include "v8/include/v8.h"

#if defined(V8_USE_EXTERNAL_STARTUP_DATA)
#include "gin/public/v8_snapshot_file_type.h"
#endif

namespace blink {

#if BUILDFLAG(IS_WIN)
// Defined in v8_initializer_win.cc.
v8::FilterETWSessionByURLResult FilterETWSessionByURLCallback(
    v8::Local<v8::Context> context,
    const std::string& json_payload);
#endif  // BUILDFLAG(IS_WIN)

namespace {

String ExtractMessageForConsole(v8::Isolate* isolate,
                                v8::Local<v8::Value> data) {
  DOMException* exception = V8DOMException::ToWrappable(isolate, data);
  return exception ? exception->ToStringForConsole() : String();
}

mojom::ConsoleMessageLevel MessageLevelFromNonFatalErrorLevel(int error_level) {
  mojom::ConsoleMessageLevel level = mojom::ConsoleMessageLevel::kError;
  switch (error_level) {
    case v8::Isolate::kMessageDebug:
      level = mojom::ConsoleMessageLevel::kVerbose;
      break;
    case v8::Isolate::kMessageLog:
    case v8::Isolate::kMessageInfo:
      level = mojom::ConsoleMessageLevel::kInfo;
      break;
    case v8::Isolate::kMessageWarning:
      level = mojom::ConsoleMessageLevel::kWarning;
      break;
    case v8::Isolate::kMessageError:
      level = mojom::ConsoleMessageLevel::kInfo;
      break;
    default:
      NOTREACHED();
  }
  return level;
}

// Converts a v8::String |source| to a blink String, limited to the first
// |max_length| characters. If |max_length| is set to 0, the full string is
// used.
String ToBlinkString(v8::Local<v8::Context> context,
                     v8::Local<v8::String> source,
                     size_t max_length) {
  uint32_t source_length = source->Length();
  size_t len = max_length == 0 ? source_length
                               : std::min<size_t>(max_length, source_length);
  if (len == 0) {
    return String();
  }
  base::span<UChar> buffer;
  String result =
      String::CreateUninitialized(base::checked_cast<uint32_t>(len), buffer);
  DCHECK_LE(len, std::numeric_limits<uint32_t>::max());
  source->WriteV2(v8::Isolate::GetCurrent(), 0, static_cast<uint32_t>(len),
                  reinterpret_cast<uint16_t*>(buffer.data()));
  return result;
}

// NOTE: when editing this, please also edit the error messages we throw when
// the size is exceeded (see uses of the constant), which use the human-friendly
// "8MB" text.
const size_t kWasmWireBytesLimit = 1 << 23;

void AddCrashKey(v8::CrashKeyId id, const std::string& value) {
  using base::debug::AllocateCrashKeyString;
  using base::debug::CrashKeySize;
  using base::debug::SetCrashKeyString;

  switch (id) {
    case v8::CrashKeyId::kIsolateAddress:
      static auto* const isolate_address =
          AllocateCrashKeyString("v8_isolate_address", CrashKeySize::Size32);
      SetCrashKeyString(isolate_address, value);
      break;
    case v8::CrashKeyId::kReadonlySpaceFirstPageAddress:
      static auto* const ro_space_firstpage_address = AllocateCrashKeyString(
          "v8_ro_space_firstpage_address", CrashKeySize::Size32);
      SetCrashKeyString(ro_space_firstpage_address, value);
      break;
    case v8::CrashKeyId::kMapSpaceFirstPageAddress:
      static auto* const map_space_firstpage_address = AllocateCrashKeyString(
          "v8_map_space_firstpage_address", CrashKeySize::Size32);
      SetCrashKeyString(map_space_firstpage_address, value);
      break;
    case v8::CrashKeyId::kCodeSpaceFirstPageAddress:
      static auto* const code_space_firstpage_address = AllocateCrashKeyString(
          "v8_code_space_firstpage_address", CrashKeySize::Size32);
      SetCrashKeyString(code_space_firstpage_address, value);
      break;
    case v8::CrashKeyId::kDumpType:
      static auto* const dump_type =
          AllocateCrashKeyString("dump-type", CrashKeySize::Size32);
      SetCrashKeyString(dump_type, value);
      break;
    default:
      // Doing nothing for new keys is a valid option. Having this case allows
      // to introduce new CrashKeyId's without triggering a build break.
      break;
  }
}

base::debug::CrashKeySize ToCrashKeySize(v8::CrashKeySize size) {
  using base::debug::CrashKeySize;

  switch (size) {
    case v8::CrashKeySize::Size32:
      return CrashKeySize::Size32;
    case v8::CrashKeySize::Size64:
      return CrashKeySize::Size64;
    case v8::CrashKeySize::Size256:
      return CrashKeySize::Size256;
    case v8::CrashKeySize::Size1024:
      return CrashKeySize::Size1024;
    default:
      NOTREACHED();
  }
}

v8::CrashKey AllocateCrashKeyString(const char key[], v8::CrashKeySize size) {
  return AllocateCrashKeyString(key, ToCrashKeySize(size));
}

void SetCrashKeyString(v8::CrashKey key, std::string_view value) {
  SetCrashKeyString(reinterpret_cast<base::debug::CrashKeyString*>(key), value);
}

}  // namespace

void V8Initializer::MessageHandlerInMainThread(v8::Local<v8::Message> message,
                                               v8::Local<v8::Value> data) {
  DCHECK(IsMainThread());
  v8::Isolate* isolate = v8::Isolate::GetCurrent();

  if (isolate->GetEnteredOrMicrotaskContext().IsEmpty())
    return;

  // If called during context initialization, there will be no entered context.
  ScriptState* script_state = ScriptState::ForCurrentRealm(isolate);
  if (!script_state->ContextIsValid())
    return;

  ExecutionContext* context = ExecutionContext::From(script_state);

  UseCounter::Count(context, WebFeature::kUnhandledExceptionCountInMainThread);
  base::UmaHistogramBoolean("V8.UnhandledExceptionCountInMainThread", true);

  SourceLocation* location = CaptureSourceLocation(isolate, message, context);

  if (message->ErrorLevel() != v8::Isolate::kMessageError) {
    context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
        mojom::ConsoleMessageSource::kJavaScript,
        MessageLevelFromNonFatalErrorLevel(message->ErrorLevel()),
        ToCoreStringWithNullCheck(isolate, message->Get()), location));
    return;
  }

  const auto sanitize_script_errors = message->IsSharedCrossOrigin()
                                          ? SanitizeScriptErrors::kDoNotSanitize
                                          : SanitizeScriptErrors::kSanitize;

  ErrorEvent* event = ErrorEvent::Create(
      ToCoreStringWithNullCheck(isolate, message->Get()), location,
      ScriptValue(isolate, data), &script_state->World());

  String message_for_console = ExtractMessageForConsole(isolate, data);
  if (!message_for_console.empty())
    event->SetUnsanitizedMessage(message_for_console);

  context->DispatchErrorEvent(event, sanitize_script_errors);
}

void V8Initializer::MessageHandlerInWorker(v8::Local<v8::Message> message,
                                           v8::Local<v8::Value> data) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  // During the frame teardown, there may not be a valid context.
  ScriptState* script_state = ScriptState::ForCurrentRealm(isolate);
  if (!script_state->ContextIsValid())
    return;

  ExecutionContext* context = ExecutionContext::From(script_state);
  CHECK(context);

  UseCounter::Count(context, WebFeature::kUnhandledExceptionCountInWorker);
  base::UmaHistogramBoolean("V8.UnhandledExceptionCountInWorker", true);

  SourceLocation* location = CaptureSourceLocation(isolate, message, context);

  if (message->ErrorLevel() != v8::Isolate::kMessageError) {
    context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
        mojom::ConsoleMessageSource::kJavaScript,
        MessageLevelFromNonFatalErrorLevel(message->ErrorLevel()),
        ToCoreStringWithNullCheck(isolate, message->Get()), location));
    return;
  }

  ErrorEvent* event = ErrorEvent::Create(
      ToCoreStringWithNullCheck(isolate, message->Get()), location,
      ScriptValue(isolate, data), &script_state->World());

  const auto sanitize_script_errors = message->IsSharedCrossOrigin()
                                          ? SanitizeScriptErrors::kDoNotSanitize
                                          : SanitizeScriptErrors::kSanitize;

  // If execution termination has been triggered as part of constructing
  // the error event from the v8::Message, quietly leave.
  if (!isolate->IsExecutionTerminating()) {
    ExecutionContext::From(script_state)
        ->DispatchErrorEvent(event, sanitize_script_errors);
  }
}

static void PromiseRejectHandler(v8::PromiseRejectMessage data,
                                 RejectedPromises& rejected_promises,
                                 ScriptState* script_state) {
  if (data.GetEvent() == v8::kPromiseHandlerAddedAfterReject) {
    rejected_promises.HandlerAdded(data);
    return;
  } else if (data.GetEvent() != v8::kPromiseRejectWithNoHandler) {
    // The only other types are kPromiseRejectAfterResolved and
    // kPromiseResolveAfterResolved. Those are being deprecated in
    // V8 so we want to avoid explicitly mentioning them here.
    return;
  }

  v8::Isolate* isolate = script_state->GetIsolate();
  ExecutionContext* context = ExecutionContext::From(script_state);

  v8::Local<v8::Value> exception = data.GetValue();
  String error_message;
  SanitizeScriptErrors sanitize_script_errors = SanitizeScriptErrors::kSanitize;
  SourceLocation* location;

  v8::Local<v8::Message> message =
      v8::Exception::CreateMessage(isolate, exception);
  if (!message.IsEmpty()) {
    // message->Get() can be empty here. https://crbug.com/450330
    error_message = ToCoreStringWithNullCheck(isolate, message->Get());
    location = CaptureSourceLocation(isolate, message, context);
    if (message->IsSharedCrossOrigin())
      sanitize_script_errors = SanitizeScriptErrors::kDoNotSanitize;
  } else {
    location = MakeGarbageCollected<SourceLocation>(context->Url().GetString(),
                                                    String(), 0, 0, nullptr);
  }

  String message_for_console =
      ExtractMessageForConsole(isolate, data.GetValue());
  if (!message_for_console.empty()) {
    error_message = std::move(message_for_console);
  }

  rejected_promises.RejectedWithNoHandler(script_state, data, error_message,
                                          location, sanitize_script_errors);
}

// static
void V8Initializer::PromiseRejectHandlerInMainThread(
    v8::PromiseRejectMessage data) {
  DCHECK(IsMainThread());

  v8::Isolate* isolate = v8::Isolate::GetCurrent();

  // TODO(ikilpatrick): Remove this check, extensions tests that use
  // extensions::ModuleSystemTest incorrectly don't have a valid script state.
  LocalDOMWindow* window = CurrentDOMWindow(isolate);
  if (!window || !window->IsCurrentlyDisplayedInFrame())
    return;

  // Bail out if called during context initialization.
  ScriptState* script_state = ScriptState::ForCurrentRealm(isolate);
  if (!script_state->ContextIsValid())
    return;

  RejectedPromises* rejected_promises =
      &window->GetAgent()->GetRejectedPromises();
  PromiseRejectHandler(data, *rejected_promises, script_state);
}

void V8Initializer::ExceptionPropagationCallback(
    v8::ExceptionPropagationMessage v8_message) {
  v8::Isolate* isolate = v8_message.GetIsolate();
  if (V8PerIsolateData::From(isolate)->OmitExceptionContextInformation()) {
    return;
  }

  ScriptState* script_state =
      ScriptState::MaybeFrom(isolate, isolate->GetCurrentContext());
  if (!script_state) {
    return;
  }

  v8::Local<v8::Object> exception = v8_message.GetException();

  v8::ExceptionContext context_type = v8_message.GetExceptionContext();
  String class_name = ToCoreString(isolate, v8_message.GetInterfaceName());
  String property_name = ToCoreString(isolate, v8_message.GetPropertyName());
  if ((context_type == v8::ExceptionContext::kAttributeGet &&
       property_name.starts_with("get ")) ||
      (context_type == v8::ExceptionContext::kAttributeSet &&
       property_name.starts_with("set "))) {
    property_name = property_name.substr(4);
  }
  if (property_name == "[Symbol.toPrimitive]") {
    property_name = String();
  }
  if (context_type == v8::ExceptionContext::kConstructor) {
    // Constructors are reported by v8 as the property name, but
    // our plumbing expects it as the class name.
    class_name = property_name;
  }
  DCHECK(class_name.Is8Bit());

  for (auto* dictionary_context =
           V8PerIsolateData::From(isolate)->TopOfDictionaryStack();
       dictionary_context;
       dictionary_context = dictionary_context->Previous()) {
    ApplyContextToException(script_state, exception,
                            v8::ExceptionContext::kAttributeGet,
                            dictionary_context->DictionaryName(),
                            dictionary_context->PropertyName());
  }

  ApplyContextToException(script_state, exception, context_type,
                          class_name.Utf8().data(), property_name);
}

static void PromiseRejectHandlerInWorker(v8::PromiseRejectMessage data) {
  // Bail out if called during context initialization.
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  ScriptState* script_state = ScriptState::ForCurrentRealm(isolate);
  if (!script_state->ContextIsValid())
    return;

  ExecutionContext* execution_context = ExecutionContext::From(script_state);
  if (!execution_context)
    return;

  ExecutionContext* root_worker_context =
      execution_context->IsShadowRealmGlobalScope()
          ? To<ShadowRealmGlobalScope>(execution_context)
                ->GetRootInitiatorExecutionContext()
          : execution_context;
  DCHECK(root_worker_context->IsWorkerOrWorkletGlobalScope());

  auto* script_controller =
      To<WorkerOrWorkletGlobalScope>(root_worker_context)->ScriptController();
  DCHECK(script_controller);

  PromiseRejectHandler(data, *script_controller->GetRejectedPromises(),
                       script_state);
}

// static
void V8Initializer::FailedAccessCheckCallbackInMainThread(
    v8::Local<v8::Object> holder,
    v8::AccessType,
    v8::Local<v8::Value>) {
  BindingSecurity::FailedAccessCheckFor(holder);
}

// Check whether Content Security Policy allows script execution.
static bool ContentSecurityPolicyCodeGenerationCheck(
    v8::Local<v8::Context> context,
    v8::Local<v8::String> source) {
  if (ExecutionContext* execution_context = ToExecutionContext(context)) {
    // Note this callback is only triggered for contexts which have eval
    // disabled. Hence we don't need to handle the case of isolated world
    // contexts with no CSP specified. (They should be exempt from the page CSP.
    // See crbug.com/982388.)

    if (ContentSecurityPolicy* policy =
            execution_context->GetContentSecurityPolicyForCurrentWorld()) {
      v8::Context::Scope scope(context);
      return policy->AllowEval(ReportingDisposition::kReport,
                               ContentSecurityPolicy::kWillThrowException,
                               ToBlinkString(context, source, 0));
    }
  }
  return false;
}

// Check whether Content Security Policy allows 'eval' in a Trusted Types
// context, via the "script-src 'trusted-types-eval'" directive + keyword.
static bool ContentSecurityPolicyTrustedTypesCodeGenerationCheck(
    v8::Local<v8::Context> context) {
  if (ExecutionContext* execution_context = ToExecutionContext(context)) {
    if (ContentSecurityPolicy* policy =
            execution_context->GetContentSecurityPolicyForCurrentWorld()) {
      v8::Context::Scope scope(context);
      return policy->AllowTrustedTypesEval(
          ReportingDisposition::kReport,
          ContentSecurityPolicy::kWillThrowException);
    }
  }
  return false;
}

std::pair<bool, v8::MaybeLocal<v8::String>> TrustedTypesCodeGenerationCheck(
    v8::Local<v8::Context> context,
    v8::Local<v8::Value> source,
    bool is_code_like) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  // If the input is not a string or TrustedScript, pass it through.
  if (!source->IsString() && !is_code_like &&
      !V8TrustedScript::HasInstance(isolate, source)) {
    return {true, v8::MaybeLocal<v8::String>()};
  }

  // If CSP allows eval in a Trusted Types environment ('trusted-types-eval'),
  // then pass it through.
  if (RuntimeEnabledFeatures::TrustedTypesHTMLEnabled()) {
    if (ContentSecurityPolicyTrustedTypesCodeGenerationCheck(context)) {
      return {true, v8::MaybeLocal<v8::String>()};
    }
  }

  v8::TryCatch try_catch(isolate);
  V8UnionStringOrTrustedScript* string_or_trusted_script =
      NativeValueTraits<V8UnionStringOrTrustedScript>::NativeValue(
          isolate, source, PassThroughException(isolate));
  if (try_catch.HasCaught()) {
    // The input was a string or TrustedScript but the conversion failed.
    // Block, just in case.
    return {false, v8::MaybeLocal<v8::String>()};
  }

  if (is_code_like && string_or_trusted_script->IsString()) {
    string_or_trusted_script->Set(MakeGarbageCollected<TrustedScript>(
        string_or_trusted_script->GetAsString()));
  }

  String stringified_source = TrustedTypesCheckForScript(
      string_or_trusted_script, ToExecutionContext(context),
      trusted_types_names::kEval, g_empty_atom, PassThroughException(isolate));
  if (try_catch.HasCaught()) {
    return {false, v8::MaybeLocal<v8::String>()};
  }

  if (RuntimeEnabledFeatures::TrustedTypesHTMLEnabled()) {
    // This check implements steps 1.2.8 of
    // https://w3c.github.io/webappsec-csp/#can-compile-strings
    bool has_changed =
        stringified_source !=
        (string_or_trusted_script->IsString()
             ? string_or_trusted_script->GetAsString()
             : string_or_trusted_script->GetAsTrustedScript()->toString());
    if (has_changed) {
      return {false, v8::MaybeLocal<v8::String>()};
    }
  }

  return {true, V8String(isolate, stringified_source)};
}

// static
v8::ModifyCodeGenerationFromStringsResult
V8Initializer::CodeGenerationCheckCallbackInMainThread(
    v8::Local<v8::Context> context,
    v8::Local<v8::Value> source,
    bool is_code_like) {
  // The code generation callback should only be installed on "normal" JS
  // contexts, which in turn should always have an associated ExecutionContext.
  // If this invariant holds, we can simplify this code a little bit.
  // We're probing this invariant to ensure it won't cause issues in practice.
  // See also: Discussion on crrev.com/c/7207201.
  CHECK(ToExecutionContext(context), base::NotFatalUntil::M150);

  // The TC39 "Dynamic Code Brand Check" feature is currently behind a flag.
  if (!RuntimeEnabledFeatures::TrustedTypesUseCodeLikeEnabled())
    is_code_like = false;

  // With Trusted Types, we always run the TT check first because of reporting,
  // and because a default policy might want to stringify or modify the original
  // source. When TT enforcement is disabled, codegen is always allowed, and we
  // just use the check to stringify any trusted type source.
  bool codegen_allowed_by_tt = false;
  v8::MaybeLocal<v8::String> stringified_source;
  std::tie(codegen_allowed_by_tt, stringified_source) =
      TrustedTypesCodeGenerationCheck(context, source, is_code_like);

  if (!codegen_allowed_by_tt) {
    return {false, v8::MaybeLocal<v8::String>()};
  }

  if (stringified_source.IsEmpty()) {
    return {true, v8::MaybeLocal<v8::String>()};
  }

  if (!ContentSecurityPolicyCodeGenerationCheck(
          context, stringified_source.ToLocalChecked())) {
    return {false, v8::MaybeLocal<v8::String>()};
  }

  return {true, std::move(stringified_source)};
}

bool V8Initializer::WasmCodeGenerationCheckCallback(
    v8::Local<v8::Context> context,
    v8::Local<v8::String> source) {
  ExecutionContext* execution_context = ToExecutionContext(context);
  if (!execution_context) {
    return false;
  }
  ContentSecurityPolicy* policy = execution_context->GetContentSecurityPolicy();
  if (!policy || !policy->AllowWasmCodeGeneration(
                     ReportingDisposition::kReport,
                     ContentSecurityPolicy::kWillThrowException,
                     ToBlinkString(context, source,
                                   ContentSecurityPolicy::kMaxSampleLength))) {
    return false;
  }

  // Set a crash key so we know if a crash report could have been caused by
  // Wasm.
  [[maybe_unused]] static bool crash_key_set = [] {
    static crash_reporter::CrashKeyString<1> has_wasm_key("has-wasm");
    has_wasm_key.Set("1");
    return true;
  }();
  return true;
}

void V8Initializer::WasmAsyncResolvePromiseCallback(
    v8::Isolate* isolate,
    v8::Local<v8::Context> context,
    v8::Local<v8::Promise::Resolver> resolver,
    v8::Local<v8::Value> compilation_result,
    v8::WasmAsyncSuccess success) {
  ScriptState* script_state = ScriptState::MaybeFrom(isolate, context);
  if (!script_state ||
      !IsInParallelAlgorithmRunnable(ExecutionContext::From(script_state),
                                     script_state)) {
    return;
  }
  v8::MicrotasksScope microtasks_scope(
      isolate, context->GetMicrotaskQueue(),
      v8::MicrotasksScope::kDoNotRunMicrotasks);
  if (success == v8::WasmAsyncSuccess::kSuccess) {
    CHECK(resolver->Resolve(context, compilation_result).FromJust());
  } else {
    CHECK(resolver->Reject(context, compilation_result).FromJust());
  }
}

namespace {
bool SharedArrayBufferConstructorEnabledCallback(
    v8::Local<v8::Context> context) {
  ExecutionContext* execution_context = ToExecutionContext(context);
  if (!execution_context)
    return false;
  return execution_context->SharedArrayBufferTransferAllowed();
}

v8::Local<v8::Value> NewRangeException(v8::Isolate* isolate,
                                       const char* message) {
  return v8::Exception::RangeError(
      v8::String::NewFromOneByte(isolate,
                                 reinterpret_cast<const uint8_t*>(message),
                                 v8::NewStringType::kNormal)
          .ToLocalChecked());
}

void ThrowRangeException(v8::Isolate* isolate, const char* message) {
  isolate->ThrowException(NewRangeException(isolate, message));
}

BASE_FEATURE(kWebAssemblyUnlimitedSyncCompilation,
             base::FEATURE_DISABLED_BY_DEFAULT);

bool WasmModuleOverride(const v8::FunctionCallbackInfo<v8::Value>& args) {
  // Return false if we want the base behavior to proceed.
  if (!IsMainThread() || args.Length() < 1 ||
      base::FeatureList::IsEnabled(kWebAssemblyUnlimitedSyncCompilation)) {
    return false;
  }
  v8::Local<v8::Value> source = args[0];
  if ((source->IsArrayBuffer() &&
       v8::Local<v8::ArrayBuffer>::Cast(source)->ByteLength() >
           kWasmWireBytesLimit) ||
      (source->IsArrayBufferView() &&
       v8::Local<v8::ArrayBufferView>::Cast(source)->ByteLength() >
           kWasmWireBytesLimit)) {
    ThrowRangeException(
        args.GetIsolate(),
        "WebAssembly.Compile is disallowed on the main thread, "
        "if the buffer size is larger than 8MB. Use "
        "WebAssembly.compile, compile on a worker thread, or use the flag "
        "`--enable-features=WebAssemblyUnlimitedSyncCompilation`.");
    // Return true because we injected new behavior and we do not
    // want the default behavior.
    return true;
  }
  return false;
}

bool WasmInstanceOverride(const v8::FunctionCallbackInfo<v8::Value>& args) {
  // Return false if we want the base behavior to proceed.
  if (!IsMainThread() || args.Length() < 1 ||
      base::FeatureList::IsEnabled(kWebAssemblyUnlimitedSyncCompilation)) {
    return false;
  }
  v8::Local<v8::Value> source = args[0];
  if (!source->IsWasmModuleObject())
    return false;

  v8::CompiledWasmModule compiled_module =
      v8::Local<v8::WasmModuleObject>::Cast(source)->GetCompiledModule();
  if (compiled_module.GetWireBytesRef().size() > kWasmWireBytesLimit) {
    ThrowRangeException(
        args.GetIsolate(),
        "WebAssembly.Instance is disallowed on the main thread, "
        "if the buffer size is larger than 8MB. Use "
        "WebAssembly.instantiate, or use the flag "
        "`--enable-features=WebAssemblyUnlimitedSyncCompilation`.");
    return true;
  }
  return false;
}

bool WasmCustomDescriptorsEnabledCallback(v8::Local<v8::Context> context) {
  ExecutionContext* execution_context = ToExecutionContext(context);
  if (!execution_context) {
    return false;
  }
  return RuntimeEnabledFeatures::WebAssemblyCustomDescriptorsV2Enabled(
      execution_context);
}

v8::MaybeLocal<v8::Promise> HostImportModuleWithPhaseDynamically(
    v8::Local<v8::Context> context,
    v8::Local<v8::Data> v8_host_defined_options,
    v8::Local<v8::Value> v8_referrer_resource_url,
    v8::Local<v8::String> v8_specifier,
    v8::ModuleImportPhase import_phase,
    v8::Local<v8::FixedArray> v8_import_attributes) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  ScriptState* script_state = ScriptState::From(isolate, context);

  Modulator* modulator = Modulator::From(script_state);
  if (!modulator) {
    // Inactive browsing context (detached frames) doesn't have a modulator.
    // We chose to return a rejected promise (which may never get to catch(),
    // since MicrotaskQueue for a detached frame is never consumed).
    //
    // This is a hack to satisfy V8 API expectation, which are:
    // - return non-empty v8::Promise value
    //   (can either be fulfilled/rejected), or
    // - throw exception && return Empty value
    // See crbug.com/972960 .
    //
    // We use the v8 promise API directly here.
    // We can't use ScriptPromiseResolverBase here since it assumes a valid
    // ScriptState.
    v8::Local<v8::Promise::Resolver> resolver;
    if (!v8::Promise::Resolver::New(script_state->GetContext())
             .ToLocal(&resolver)) {
      // Note: V8 should have thrown an exception in this case,
      //       so we return Empty.
      return v8::MaybeLocal<v8::Promise>();
    }

    v8::Local<v8::Promise> promise = resolver->GetPromise();
    v8::Local<v8::Value> error = V8ThrowException::CreateError(
        script_state->GetIsolate(),
        "Cannot import module from an inactive browsing context.");
    resolver->Reject(script_state->GetContext(), error).ToChecked();
    return promise;
  }

  String specifier =
      ToCoreStringWithNullCheck(script_state->GetIsolate(), v8_specifier);
  KURL referrer_resource_url;
  if (v8_referrer_resource_url->IsString()) {
    String referrer_resource_url_str =
        ToCoreString(script_state->GetIsolate(),
                     v8::Local<v8::String>::Cast(v8_referrer_resource_url));
    if (!referrer_resource_url_str.empty())
      referrer_resource_url = KURL(NullUrl(), referrer_resource_url_str);
  }

  ModuleRequest module_request(
      specifier, TextPosition::MinimumPosition(),
      ModuleRecord::ToBlinkImportAttributes(
          v8::Local<v8::Module>(), v8_import_attributes,
          /*v8_import_attributes_has_positions=*/false),
      import_phase);

  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLAny>>(
      script_state,
      ExceptionContext(v8::ExceptionContext::kUnknown, "", "import"));

  String invalid_attribute_key;
  if (module_request.HasInvalidImportAttributeKey(&invalid_attribute_key)) {
    resolver->Reject(V8ThrowException::CreateTypeError(
        script_state->GetIsolate(),
        StrCat({"Invalid attribute key \"", invalid_attribute_key, "\"."})));
  } else {
    ReferrerScriptInfo referrer_info =
        ReferrerScriptInfo::FromV8HostDefinedOptions(
            context, v8_host_defined_options, referrer_resource_url);

    modulator->ResolveDynamically(module_request, referrer_info, resolver);
  }

  return resolver->Promise().V8Promise();
}

v8::MaybeLocal<v8::Promise> HostImportModuleDynamically(
    v8::Local<v8::Context> context,
    v8::Local<v8::Data> v8_host_defined_options,
    v8::Local<v8::Value> v8_referrer_resource_url,
    v8::Local<v8::String> v8_specifier,
    v8::Local<v8::FixedArray> v8_import_attributes) {
  return HostImportModuleWithPhaseDynamically(
      context, v8_host_defined_options, v8_referrer_resource_url, v8_specifier,
      v8::ModuleImportPhase::kEvaluation, v8_import_attributes);
}

// https://html.spec.whatwg.org/C/#hostgetimportmetaproperties
void HostGetImportMetaProperties(v8::Local<v8::Context> context,
                                 v8::Local<v8::Module> module,
                                 v8::Local<v8::Object> meta) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  ScriptState* script_state = ScriptState::From(isolate, context);
  v8::HandleScope handle_scope(isolate);

  Modulator* modulator = Modulator::From(script_state);
  if (!modulator)
    return;

  ModuleImportMeta host_meta = modulator->HostGetImportMetaProperties(module);

  // 6. Return « Record { [[Key]]: "url", [[Value]]: urlString }, Record {
  // [[Key]]: "resolve", [[Value]]: resolveFunction } ». [spec text]
  v8::Local<v8::String> url_key = V8String(isolate, "url");
  v8::Local<v8::String> url_value = V8String(isolate, host_meta.Url());

  v8::Local<v8::String> resolve_key = V8String(isolate, "resolve");
  v8::Local<v8::Function> resolve_value =
      host_meta.MakeResolveV8Function(modulator);
  resolve_value->SetName(resolve_key);

  meta->CreateDataProperty(context, url_key, url_value).ToChecked();
  meta->CreateDataProperty(context, resolve_key, resolve_value).ToChecked();
}

bool IsDOMExceptionWrapper(v8::Isolate* isolate, v8::Local<v8::Object> object) {
  return V8DOMException::HasInstance(isolate, object);
}

struct PrintV8OOM {
  const char* location;
  const v8::OOMDetails& details;
};

std::ostream& operator<<(std::ostream& os, const PrintV8OOM& oom_details) {
  const auto [location, details] = oom_details;
  os << "V8 " << (details.is_heap_oom ? "javascript" : "process") << " OOM ("
     << location;
  if (details.detail) {
    os << "; detail: " << details.detail;
  }
  os << ").";
  return os;
}

void EmitDevToolsEvent(v8::Isolate* isolate) {
  TRACE_EVENT_INSTANT(
      TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", "data",
      [&](perfetto::TracedValue context) {
        inspector_update_counters_event::Data(std::move(context), isolate);
      });
}

int64_t FineTemporalHostSystemUTCEpochNanosecondsCallback(
    v8::Local<v8::Context> context) {
  static TimeClamper clamper;
  base::TimeDelta delta = base::Time::Now() - base::Time::UnixEpoch();
  return clamper.ClampTimeResolution(delta, true).InNanoseconds();
}

int64_t CoarseTemporalHostSystemUTCEpochNanosecondsCallback(
    v8::Local<v8::Context> context) {
  static TimeClamper clamper;
  base::TimeDelta delta = base::Time::Now() - base::Time::UnixEpoch();
  return clamper.ClampTimeResolution(delta, false).InNanoseconds();
}

}  // namespace

// static
void V8Initializer::InitializeV8Common(v8::Isolate* isolate) {
  // Set up garbage collection before setting up anything else as V8 may trigger
  // GCs during Blink setup.
  ThreadState::Current()->AttachToIsolate(isolate, &EmitDevToolsEvent);

  isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kScoped);
  isolate->SetUseCounterCallback(&UseCounterCallback);
  isolate->SetWasmModuleCallback(WasmModuleOverride);
  isolate->SetWasmInstanceCallback(WasmInstanceOverride);
  isolate->SetWasmCustomDescriptorsEnabledCallback(
      WasmCustomDescriptorsEnabledCallback);
  isolate->SetSharedArrayBufferConstructorEnabledCallback(
      SharedArrayBufferConstructorEnabledCallback);
  isolate->SetHostImportModuleDynamicallyCallback(HostImportModuleDynamically);
  isolate->SetHostImportModuleWithPhaseDynamicallyCallback(
      HostImportModuleWithPhaseDynamically);
  isolate->SetHostInitializeImportMetaObjectCallback(
      HostGetImportMetaProperties);
  isolate->SetIsJSApiWrapperNativeErrorCallback(IsDOMExceptionWrapper);
  isolate->SetMetricsRecorder(std::make_shared<V8MetricsRecorder>(isolate));

#if BUILDFLAG(IS_WIN)
  isolate->SetFilterETWSessionByURL2Callback(FilterETWSessionByURLCallback);
#endif  // BUILDFLAG(IS_WIN)

  V8ContextSnapshot::EnsureInterfaceTemplates(isolate);

  WasmResponseExtensions::Initialize(isolate);

  if (v8::HeapProfiler* profiler = isolate->GetHeapProfiler()) {
    profiler->SetGetDetachednessCallback(
        V8GCController::DetachednessFromWrapper, nullptr);
  }
}

// static
void V8Initializer::InitializeContext(v8::Local<v8::Context> context,
                                      ExecutionContext* execution_context) {
  DCHECK(execution_context);
  context->SetTemporalHostSystemUTCEpochNanosecondsCallback(
      execution_context->CrossOriginIsolatedCapability()
          ? FineTemporalHostSystemUTCEpochNanosecondsCallback
          : CoarseTemporalHostSystemUTCEpochNanosecondsCallback);
}

// Callback functions called when V8 encounters a fatal or OOM error.
// Keep them outside the anonymous namespace such that ChromeCrash recognizes
// them.
void ReportV8FatalError(const char* location, const char* message) {
  LOG(FATAL) << "V8 error: " << message << " (" << location << ").";
}

void ReportV8OOMError(const char* location, const v8::OOMDetails& details) {
  if (location) {
    static crash_reporter::CrashKeyString<64> location_key("v8-oom-location");
    location_key.Set(location);
  }

  if (details.detail) {
    static crash_reporter::CrashKeyString<128> detail_key("v8-oom-detail");
    detail_key.Set(details.detail);
  }

  LOG(ERROR) << PrintV8OOM{location, details};
  OOM_CRASH(0);
}

namespace {
class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
 public:
  ArrayBufferAllocator() : total_allocation_(0) {
    // size_t may be equivalent to uint32_t or uint64_t, cast all values to
    // uint64_t to compare.
    uint64_t virtual_size = base::SysInfo::AmountOfVirtualMemory().InBytes();
    uint64_t size_t_max = std::numeric_limits<std::size_t>::max();
    DCHECK(virtual_size < size_t_max);
    // If AmountOfVirtualMemory() returns 0, there is no limit on virtual
    // memory, do not limit the total allocation. Otherwise, Limit the total
    // allocation to reserve up to 2 GiB virtual memory space for other
    // components.
    uint64_t memory_reserve = 2ull * 1024 * 1024 * 1024;  // 2 GiB
    if (virtual_size > memory_reserve * 2) {
      max_allocation_ = static_cast<size_t>(virtual_size - memory_reserve);
    } else {
      max_allocation_ = static_cast<size_t>(virtual_size / 2);
    }
  }

  // Allocate() methods return null to signal allocation failure to V8, which
  // should respond by throwing a RangeError, per
  // http://www.ecma-international.org/ecma-262/6.0/#sec-createbytedatablock.
  void* Allocate(size_t size) override {
    if (max_allocation_ != 0 &&
        std::atomic_load(&total_allocation_) > max_allocation_ - size)
      return nullptr;
    void* result = ArrayBufferContents::AllocateMemoryOrNull(
        size, ArrayBufferContents::kZeroInitialize);
    if (max_allocation_ != 0 && result)
      total_allocation_.fetch_add(size, std::memory_order_relaxed);
    return result;
  }

  void* AllocateUninitialized(size_t size) override {
    if (max_allocation_ != 0 &&
        std::atomic_load(&total_allocation_) > max_allocation_ - size)
      return nullptr;
    void* result = ArrayBufferContents::AllocateMemoryOrNull(
        size, ArrayBufferContents::kDontInitialize);
    if (max_allocation_ != 0 && result)
      total_allocation_.fetch_add(size, std::memory_order_relaxed);
    return result;
  }

  void Free(void* data, size_t size) override {
    if (max_allocation_ != 0 && data)
      total_allocation_.fetch_sub(size, std::memory_order_relaxed);
    ArrayBufferContents::FreeMemory(data);
  }

 private:
  // Total memory allocated in bytes.
  std::atomic_size_t total_allocation_;
  // If |max_allocation_| is 0, skip these atomic operations on
  // |total_allocation_|.
  size_t max_allocation_;
};

#ifdef V8_ENABLE_SANDBOX

// The ArrayBuffer partition is placed inside the V8 sandbox and we can just
// reuse the ArrayBufferContents as allocator that will delegate to
// PartitionAlloc.
class InSandboxAllocator final : public v8::Allocator {
 public:
  InSandboxAllocator() = default;
  void* Allocate(size_t size) override {
    return ArrayBufferContents::AllocateMemoryOrNull(
        size, ArrayBufferContents::kZeroInitialize);
  }
  void* AllocateUninitialized(size_t size) override {
    return ArrayBufferContents::AllocateMemoryOrNull(
        size, ArrayBufferContents::kDontInitialize);
  }
  void* AllocateUninitializedOrCrash(size_t size) override {
    void* result = ArrayBufferContents::AllocateMemoryOrNull(
        size, ArrayBufferContents::kDontInitialize);
    if (!result) {
      OOM_CRASH(size);
    }
    return result;
  }
  void Free(void* data) override { ArrayBufferContents::FreeMemory(data); }
};

#endif  // V8_ENABLE_SANDBOX

V8PerIsolateData::V8ContextSnapshotMode GetV8ContextSnapshotMode() {
#if BUILDFLAG(USE_V8_CONTEXT_SNAPSHOT)
  if (Platform::Current()->IsTakingV8ContextSnapshot())
    return V8PerIsolateData::V8ContextSnapshotMode::kTakeSnapshot;
  if (gin::GetLoadedSnapshotFileType() ==
      gin::V8SnapshotFileType::kWithAdditionalContext) {
    return V8PerIsolateData::V8ContextSnapshotMode::kUseSnapshot;
  }
#endif  // BUILDFLAG(USE_V8_CONTEXT_SNAPSHOT)
  return V8PerIsolateData::V8ContextSnapshotMode::kDontUseSnapshot;
}

}  // namespace

void V8Initializer::InitializeIsolateHolder(
    const intptr_t* reference_table,
    const std::string& js_command_line_flags) {
  DEFINE_STATIC_LOCAL(ArrayBufferAllocator, array_buffer_allocator, ());
  gin::IsolateHolder::Initialize(
      gin::IsolateHolder::kNonStrictMode, &array_buffer_allocator,
      reference_table, js_command_line_flags,
      Platform::Current()->DisallowV8FeatureFlagOverrides(), ReportV8FatalError,
      ReportV8OOMError);
}

void V8Initializer::InitializeInSandboxAllocator() {
#ifdef V8_ENABLE_SANDBOX
  v8::V8::SetInSandboxAllocator(std::make_shared<InSandboxAllocator>());
#endif
}

v8::Isolate* V8Initializer::InitializeMainThread() {
  DCHECK(IsMainThread());
  ThreadScheduler* scheduler = ThreadScheduler::Current();

  V8PerIsolateData::V8ContextSnapshotMode snapshot_mode =
      GetV8ContextSnapshotMode();
  v8::CreateHistogramCallback create_histogram_callback = nullptr;
  v8::AddHistogramSampleCallback add_histogram_sample_callback = nullptr;
  // We don't log histograms when taking a snapshot.
  if (snapshot_mode != V8PerIsolateData::V8ContextSnapshotMode::kTakeSnapshot) {
    create_histogram_callback = CreateHistogram;
    add_histogram_sample_callback = AddHistogramSample;
  }
  v8::Isolate* isolate = V8PerIsolateData::Initialize(
      scheduler->V8TaskRunner(), scheduler->V8UserVisibleTaskRunner(),
      scheduler->V8BestEffortTaskRunner(), snapshot_mode,
      create_histogram_callback, add_histogram_sample_callback,
      ThreadState::Current()->ReleaseCppHeap());
  scheduler->SetV8Isolate(isolate);

  // ThreadState::isolate_ needs to be set before setting the EmbedderHeapTracer
  // as setting the tracer indicates that a V8 garbage collection should trace
  // over to Blink.
  DCHECK(ThreadStateStorage::MainThreadStateStorage());

  InitializeV8Common(isolate);

  isolate->AddMessageListenerWithErrorLevel(
      MessageHandlerInMainThread,
      v8::Isolate::kMessageError | v8::Isolate::kMessageWarning |
          v8::Isolate::kMessageInfo | v8::Isolate::kMessageDebug |
          v8::Isolate::kMessageLog);
  isolate->SetFailedAccessCheckCallbackFunction(
      V8Initializer::FailedAccessCheckCallbackInMainThread);
  isolate->SetModifyCodeGenerationFromStringsCallback(
      CodeGenerationCheckCallbackInMainThread);
  isolate->SetAllowWasmCodeGenerationCallback(WasmCodeGenerationCheckCallback);
  isolate->SetWasmAsyncResolvePromiseCallback(WasmAsyncResolvePromiseCallback);
  if (RuntimeEnabledFeatures::V8IdleTasksEnabled()) {
    V8PerIsolateData::EnableIdleTasks(
        isolate, std::make_unique<V8IdleTaskRunner>(scheduler));
  }

  isolate->SetPromiseRejectCallback(PromiseRejectHandlerInMainThread);
  isolate->SetExceptionPropagationCallback(ExceptionPropagationCallback);

  V8PerIsolateData::From(isolate)->SetThreadDebugger(
      std::make_unique<MainThreadDebugger>(isolate));

  isolate->SetHostCreateShadowRealmContextCallback(
      OnCreateShadowRealmV8Context);

  if (Platform::Current()->IsolateStartsInBackground()) {
    // If we do not track widget visibility, then assume conservatively that
    // the isolate is in background. This reduces memory usage.
    isolate->SetPriority(v8::Isolate::Priority::kBestEffort);
  }

  // Crash key API is not thread-safe, so we only set it up on the main thread.
  isolate->SetAddCrashKeyCallback(AddCrashKey);
  isolate->SetCrashKeyStringCallbacks(AllocateCrashKeyString,
                                      SetCrashKeyString);

  return isolate;
}

// Stack size for workers is limited to 500KB because default stack size for
// secondary threads is 512KB on macOS. See GetDefaultThreadStackSize() in
// base/threading/platform_thread_apple.mm for details.
//
// For 32-bit Windows, the stack region always starts with an odd number of
// reserved pages, followed by two guard pages, followed by the committed
// memory for the stack, and the worker stack size need to be reduced
// (https://crbug.com/1412239).
#if defined(ARCH_CPU_32_BITS) && BUILDFLAG(IS_WIN)
static const int kWorkerMaxStackSize = 492 * 1024;
#else
static const int kWorkerMaxStackSize = 500 * 1024;
#endif

void V8Initializer::InitializeWorker(v8::Isolate* isolate) {
  InitializeV8Common(isolate);

  isolate->AddMessageListenerWithErrorLevel(
      MessageHandlerInWorker,
      v8::Isolate::kMessageError | v8::Isolate::kMessageWarning |
          v8::Isolate::kMessageInfo | v8::Isolate::kMessageDebug |
          v8::Isolate::kMessageLog);

  isolate->SetStackLimit(GetCurrentStackPosition() - kWorkerMaxStackSize);
  isolate->SetPromiseRejectCallback(PromiseRejectHandlerInWorker);
  isolate->SetExceptionPropagationCallback(ExceptionPropagationCallback);
  isolate->SetModifyCodeGenerationFromStringsCallback(
      CodeGenerationCheckCallbackInMainThread);
  isolate->SetAllowWasmCodeGenerationCallback(WasmCodeGenerationCheckCallback);
  isolate->SetWasmAsyncResolvePromiseCallback(WasmAsyncResolvePromiseCallback);
  isolate->SetHostCreateShadowRealmContextCallback(
      OnCreateShadowRealmV8Context);
}

}  // namespace blink
