v8binding: Do not hold a cross origin ScriptState in IDL callback function

Make IDL callback function not hold a ScriptState of its
creation context when it's cross origin from the incumbent
realm.

Not holding a cross origin ScriptState, there is much
less risk to access a cross origin context.

IDL callback interface will follow the same approach in
a separate patch.

Bug: 892755, 886588, 904218
Change-Id: Ie55b436fcc5f66f4ee053ef08ad98ea68fb3a2d6
Reviewed-on: https://chromium-review.googlesource.com/c/1314023
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Hitoshi Yoshida <peria@chromium.org>
Commit-Queue: Yuki Shiino <yukishiino@chromium.org>
Cr-Commit-Position: refs/heads/master@{#609662}
diff --git a/third_party/blink/renderer/bindings/core/v8/js_based_event_listener.cc b/third_party/blink/renderer/bindings/core/v8/js_based_event_listener.cc
index 3822dd5..ee9ad069 100644
--- a/third_party/blink/renderer/bindings/core/v8/js_based_event_listener.cc
+++ b/third_party/blink/renderer/bindings/core/v8/js_based_event_listener.cc
@@ -84,10 +84,11 @@
       return;
   }
 
-  ScriptState* script_state_of_listener = GetScriptState();
-  DCHECK(script_state_of_listener);
+  ScriptState* script_state_of_listener = GetScriptStateOrReportError("invoke");
+  if (!script_state_of_listener)
+    return;  // The error is already reported.
   if (!script_state_of_listener->ContextIsValid())
-    return;
+    return;  // Silently fail.
 
   ScriptState::Scope listener_script_state_scope(script_state_of_listener);
 
diff --git a/third_party/blink/renderer/bindings/core/v8/js_based_event_listener.h b/third_party/blink/renderer/bindings/core/v8/js_based_event_listener.h
index 11b430b..20ef9237 100644
--- a/third_party/blink/renderer/bindings/core/v8/js_based_event_listener.h
+++ b/third_party/blink/renderer/bindings/core/v8/js_based_event_listener.h
@@ -73,7 +73,15 @@
  protected:
   explicit JSBasedEventListener(ListenerType);
   virtual v8::Isolate* GetIsolate() const = 0;
+  // Returns the ScriptState of the relevant realm of the callback object.
+  // Must be used only when it's sure that the callback object is the same
+  // origin-domain.
   virtual ScriptState* GetScriptState() const = 0;
+  // Returns the ScriptState of the relevant realm of the callback object iff
+  // the callback is the same origin-domain. Otherwise, reports the error and
+  // returns nullptr.
+  virtual ScriptState* GetScriptStateOrReportError(
+      const char* operation) const = 0;
   virtual DOMWrapperWorld& GetWorld() const = 0;
 
  private:
diff --git a/third_party/blink/renderer/bindings/core/v8/js_event_handler.h b/third_party/blink/renderer/bindings/core/v8/js_event_handler.h
index 65214a8..0013fd3 100644
--- a/third_party/blink/renderer/bindings/core/v8/js_event_handler.h
+++ b/third_party/blink/renderer/bindings/core/v8/js_event_handler.h
@@ -71,8 +71,13 @@
   ScriptState* GetScriptState() const override {
     return event_handler_->CallbackRelevantScriptState();
   }
+  ScriptState* GetScriptStateOrReportError(
+      const char* operation) const override {
+    return event_handler_->CallbackRelevantScriptStateOrReportError(
+        "EventHandler", operation);
+  }
   DOMWrapperWorld& GetWorld() const override {
-    return event_handler_->CallbackRelevantScriptState()->World();
+    return event_handler_->GetWorld();
   }
 
   // Initializes |event_handler_| with |listener|. This method must be used only
diff --git a/third_party/blink/renderer/bindings/core/v8/js_event_listener.h b/third_party/blink/renderer/bindings/core/v8/js_event_listener.h
index a31902f4..dead4b4 100644
--- a/third_party/blink/renderer/bindings/core/v8/js_event_listener.h
+++ b/third_party/blink/renderer/bindings/core/v8/js_event_listener.h
@@ -63,8 +63,13 @@
   ScriptState* GetScriptState() const override {
     return event_listener_->CallbackRelevantScriptState();
   }
+  ScriptState* GetScriptStateOrReportError(
+      const char* operation) const override {
+    return event_listener_->CallbackRelevantScriptStateOrReportError(
+        "EventListener", operation);
+  }
   DOMWrapperWorld& GetWorld() const override {
-    return event_listener_->CallbackRelevantScriptState()->World();
+    return event_listener_->GetWorld();
   }
 
  private:
diff --git a/third_party/blink/renderer/bindings/templates/callback_function.cc.tmpl b/third_party/blink/renderer/bindings/templates/callback_function.cc.tmpl
index 9331774..c4e1ea8b 100644
--- a/third_party/blink/renderer/bindings/templates/callback_function.cc.tmpl
+++ b/third_party/blink/renderer/bindings/templates/callback_function.cc.tmpl
@@ -51,22 +51,22 @@
 
 {% if callback_function_name == 'EventHandlerNonNull' %}
 bool {{cpp_class}}::IsRunnableOrThrowException(IgnorePause ignore_pause) {
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptState();
+
   bool is_runnable =
       ignore_pause == IgnorePause::kIgnore ?
       IsCallbackFunctionRunnableIgnoringPause(
-          CallbackRelevantScriptState(), IncumbentScriptState()) :
+          callback_relevant_script_state, IncumbentScriptState()) :
       IsCallbackFunctionRunnable(
-          CallbackRelevantScriptState(), IncumbentScriptState());
+          callback_relevant_script_state, IncumbentScriptState());
   if (is_runnable)
     return true;
 
   // Wrapper-tracing for the callback function makes the function object and
   // its creation context alive. Thus it's safe to use the creation context
   // of the callback function here.
-  v8::HandleScope handle_scope(GetIsolate());
-  v8::Local<v8::Object> callback_object = CallbackObject();
-  CHECK(!callback_object.IsEmpty());
-  v8::Context::Scope context_scope(callback_object->CreationContext());
+  ScriptState::Scope scope(callback_relevant_script_state);
   V8ThrowException::ThrowError(
       GetIsolate(),
       ExceptionMessages::FailedToExecute(
diff --git a/third_party/blink/renderer/bindings/templates/callback_interface.cc.tmpl b/third_party/blink/renderer/bindings/templates/callback_interface.cc.tmpl
index 67f6d87..d1c5039 100644
--- a/third_party/blink/renderer/bindings/templates/callback_interface.cc.tmpl
+++ b/third_party/blink/renderer/bindings/templates/callback_interface.cc.tmpl
@@ -112,22 +112,22 @@
 
 {% if interface_name == 'EventListener' %}
 bool {{v8_class}}::IsRunnableOrThrowException(IgnorePause ignore_pause) {
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptState();
+
   bool is_runnable =
       ignore_pause == IgnorePause::kIgnore ?
       IsCallbackFunctionRunnableIgnoringPause(
-          CallbackRelevantScriptState(), IncumbentScriptState()) :
+          callback_relevant_script_state, IncumbentScriptState()) :
       IsCallbackFunctionRunnable(
-          CallbackRelevantScriptState(), IncumbentScriptState());
+          callback_relevant_script_state, IncumbentScriptState());
   if (is_runnable)
     return true;
 
   // Wrapper-tracing for the callback function makes the function object and
   // its creation context alive. Thus it's safe to use the creation context
   // of the callback function here.
-  v8::HandleScope handle_scope(GetIsolate());
-  v8::Local<v8::Object> callback_object = CallbackObject();
-  CHECK(!callback_object.IsEmpty());
-  v8::Context::Scope context_scope(callback_object->CreationContext());
+  ScriptState::Scope scope(callback_relevant_script_state);
   V8ThrowException::ThrowError(
       GetIsolate(),
       ExceptionMessages::FailedToExecute(
diff --git a/third_party/blink/renderer/bindings/templates/callback_invoke.cc.tmpl b/third_party/blink/renderer/bindings/templates/callback_invoke.cc.tmpl
index 231d687..dd34c63 100644
--- a/third_party/blink/renderer/bindings/templates/callback_invoke.cc.tmpl
+++ b/third_party/blink/renderer/bindings/templates/callback_invoke.cc.tmpl
@@ -21,8 +21,16 @@
     return_cpp_type, return_native_value_traits_tag, arguments,
     is_treat_non_object_as_null, bypass_runnability_check,
     interface_name, operation_name) %}
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "{{interface_name}}",
+          "{{operation_name}}");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<{{return_cpp_type}}>();
+  }
+
   {% if not bypass_runnability_check %}
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -43,7 +51,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -109,7 +117,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "{{operation_name}}"))
         .ToLocal(&value)) {
       return v8::Nothing<{{return_cpp_type}}>();
@@ -135,7 +143,7 @@
   {% endif %}
   {# Fill |this_arg|. #}
   {% if invoke_or_construct == 'invoke' %}
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   {% elif interface_or_function == 'callback interface' %}
   if (!IsCallbackObjectCallable()) {
     // step 11. If value's interface is not a single operation callback
@@ -146,7 +154,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
   {% endif %}
 
@@ -174,7 +182,7 @@
   //   labeled return.
   {% if arguments %}
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   {% set has_variadic_argument = arguments[-1].is_variadic %}
   {% set non_variadic_arguments = arguments | rejectattr('is_variadic') | list %}
@@ -209,7 +217,7 @@
   if (!V8ScriptRunner::CallAsConstructor(
           GetIsolate(),
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           argc,
           argv).ToLocal(&call_result)) {
     // step 11. If callResult is an abrupt completion, set completion to
@@ -220,7 +228,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_any_callback_function_optional_any_arg.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_any_callback_function_optional_any_arg.cc
index c3e866dc..fa60843 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_any_callback_function_optional_any_arg.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_any_callback_function_optional_any_arg.cc
@@ -28,7 +28,15 @@
 }
 
 v8::Maybe<ScriptValue> V8AnyCallbackFunctionOptionalAnyArg::Invoke(ScriptWrappable* callback_this_value, ScriptValue optionalAnyArg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "AnyCallbackFunctionOptionalAnyArg",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<ScriptValue>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -48,7 +56,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -62,14 +70,14 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_optionalAnyArg = optionalAnyArg.V8Value();
   constexpr int argc = 1;
@@ -80,7 +88,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -108,7 +116,15 @@
 }
 
 v8::Maybe<ScriptValue> V8AnyCallbackFunctionOptionalAnyArg::Construct(ScriptValue optionalAnyArg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "AnyCallbackFunctionOptionalAnyArg",
+          "construct");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<ScriptValue>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -128,7 +144,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -161,7 +177,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_optionalAnyArg = optionalAnyArg.V8Value();
   constexpr int argc = 1;
@@ -172,7 +188,7 @@
   if (!V8ScriptRunner::CallAsConstructor(
           GetIsolate(),
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           argc,
           argv).ToLocal(&call_result)) {
     // step 11. If callResult is an abrupt completion, set completion to
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_any_callback_function_variadic_any_args.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_any_callback_function_variadic_any_args.cc
index a50c7e13..0155e9e 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_any_callback_function_variadic_any_args.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_any_callback_function_variadic_any_args.cc
@@ -28,7 +28,15 @@
 }
 
 v8::Maybe<ScriptValue> V8AnyCallbackFunctionVariadicAnyArgs::Invoke(ScriptWrappable* callback_this_value, const Vector<ScriptValue>& arguments) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "AnyCallbackFunctionVariadicAnyArgs",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<ScriptValue>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -48,7 +56,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -62,14 +70,14 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   const int argc = 0 + arguments.size();
   v8::Local<v8::Value> argv[argc];
@@ -81,7 +89,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -109,7 +117,15 @@
 }
 
 v8::Maybe<ScriptValue> V8AnyCallbackFunctionVariadicAnyArgs::Construct(const Vector<ScriptValue>& arguments) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "AnyCallbackFunctionVariadicAnyArgs",
+          "construct");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<ScriptValue>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -129,7 +145,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -162,7 +178,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   const int argc = 0 + arguments.size();
   v8::Local<v8::Value> argv[argc];
@@ -174,7 +190,7 @@
   if (!V8ScriptRunner::CallAsConstructor(
           GetIsolate(),
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           argc,
           argv).ToLocal(&call_result)) {
     // step 11. If callResult is an abrupt completion, set completion to
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_long_callback_function.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_long_callback_function.cc
index b277f5582..bb50b29 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_long_callback_function.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_long_callback_function.cc
@@ -28,7 +28,15 @@
 }
 
 v8::Maybe<int32_t> V8LongCallbackFunction::Invoke(ScriptWrappable* callback_this_value, int32_t num1, int32_t num2) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "LongCallbackFunction",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<int32_t>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -48,7 +56,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -62,14 +70,14 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_num1 = v8::Integer::New(GetIsolate(), num1);
   v8::Local<v8::Value> v8_num2 = v8::Integer::New(GetIsolate(), num2);
@@ -81,7 +89,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_string_sequence_callback_function_long_sequence_arg.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_string_sequence_callback_function_long_sequence_arg.cc
index 57d6334..9f791d90 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_string_sequence_callback_function_long_sequence_arg.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_string_sequence_callback_function_long_sequence_arg.cc
@@ -28,7 +28,15 @@
 }
 
 v8::Maybe<Vector<String>> V8StringSequenceCallbackFunctionLongSequenceArg::Invoke(ScriptWrappable* callback_this_value, const Vector<int32_t>& arg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "StringSequenceCallbackFunctionLongSequenceArg",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<Vector<String>>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -48,7 +56,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -62,14 +70,14 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_arg = ToV8(arg, argument_creation_context, GetIsolate());
   constexpr int argc = 1;
@@ -80,7 +88,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_test_callback_interface.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_test_callback_interface.cc
index 9b5ccee..0ddc03a 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_test_callback_interface.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_test_callback_interface.cc
@@ -39,7 +39,15 @@
 }
 
 v8::Maybe<void> V8TestCallbackInterface::voidMethod(ScriptWrappable* callback_this_value) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestCallbackInterface",
+          "voidMethod");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -59,7 +67,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -74,7 +82,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "voidMethod"))
         .ToLocal(&value)) {
       return v8::Nothing<void>();
@@ -104,7 +112,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -118,7 +126,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -134,7 +142,15 @@
 }
 
 v8::Maybe<bool> V8TestCallbackInterface::booleanMethod(ScriptWrappable* callback_this_value) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestCallbackInterface",
+          "booleanMethod");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<bool>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -154,7 +170,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -169,7 +185,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "booleanMethod"))
         .ToLocal(&value)) {
       return v8::Nothing<bool>();
@@ -199,7 +215,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -213,7 +229,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -241,7 +257,15 @@
 }
 
 v8::Maybe<void> V8TestCallbackInterface::voidMethodBooleanArg(ScriptWrappable* callback_this_value, bool boolArg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestCallbackInterface",
+          "voidMethodBooleanArg");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -261,7 +285,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -276,7 +300,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "voidMethodBooleanArg"))
         .ToLocal(&value)) {
       return v8::Nothing<void>();
@@ -306,7 +330,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -314,7 +338,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_boolArg = v8::Boolean::New(GetIsolate(), boolArg);
   constexpr int argc = 1;
@@ -325,7 +349,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -341,7 +365,15 @@
 }
 
 v8::Maybe<void> V8TestCallbackInterface::voidMethodSequenceArg(ScriptWrappable* callback_this_value, const HeapVector<Member<TestInterfaceEmpty>>& sequenceArg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestCallbackInterface",
+          "voidMethodSequenceArg");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -361,7 +393,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -376,7 +408,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "voidMethodSequenceArg"))
         .ToLocal(&value)) {
       return v8::Nothing<void>();
@@ -406,7 +438,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -414,7 +446,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_sequenceArg = ToV8(sequenceArg, argument_creation_context, GetIsolate());
   constexpr int argc = 1;
@@ -425,7 +457,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -441,7 +473,15 @@
 }
 
 v8::Maybe<void> V8TestCallbackInterface::voidMethodFloatArg(ScriptWrappable* callback_this_value, float floatArg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestCallbackInterface",
+          "voidMethodFloatArg");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -461,7 +501,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -476,7 +516,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "voidMethodFloatArg"))
         .ToLocal(&value)) {
       return v8::Nothing<void>();
@@ -506,7 +546,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -514,7 +554,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_floatArg = v8::Number::New(GetIsolate(), floatArg);
   constexpr int argc = 1;
@@ -525,7 +565,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -541,7 +581,15 @@
 }
 
 v8::Maybe<void> V8TestCallbackInterface::voidMethodTestInterfaceEmptyArg(ScriptWrappable* callback_this_value, TestInterfaceEmpty* testInterfaceEmptyArg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestCallbackInterface",
+          "voidMethodTestInterfaceEmptyArg");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -561,7 +609,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -576,7 +624,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "voidMethodTestInterfaceEmptyArg"))
         .ToLocal(&value)) {
       return v8::Nothing<void>();
@@ -606,7 +654,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -614,7 +662,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_testInterfaceEmptyArg = ToV8(testInterfaceEmptyArg, argument_creation_context, GetIsolate());
   constexpr int argc = 1;
@@ -625,7 +673,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -641,7 +689,15 @@
 }
 
 v8::Maybe<void> V8TestCallbackInterface::voidMethodTestInterfaceEmptyStringArg(ScriptWrappable* callback_this_value, TestInterfaceEmpty* testInterfaceEmptyArg, const String& stringArg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestCallbackInterface",
+          "voidMethodTestInterfaceEmptyStringArg");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -661,7 +717,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -676,7 +732,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "voidMethodTestInterfaceEmptyStringArg"))
         .ToLocal(&value)) {
       return v8::Nothing<void>();
@@ -706,7 +762,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -714,7 +770,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_testInterfaceEmptyArg = ToV8(testInterfaceEmptyArg, argument_creation_context, GetIsolate());
   v8::Local<v8::Value> v8_stringArg = V8String(GetIsolate(), stringArg);
@@ -726,7 +782,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -742,7 +798,15 @@
 }
 
 v8::Maybe<void> V8TestCallbackInterface::callbackWithThisValueVoidMethodStringArg(ScriptWrappable* callback_this_value, const String& stringArg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestCallbackInterface",
+          "callbackWithThisValueVoidMethodStringArg");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -762,7 +826,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -777,7 +841,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "callbackWithThisValueVoidMethodStringArg"))
         .ToLocal(&value)) {
       return v8::Nothing<void>();
@@ -807,7 +871,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -815,7 +879,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_stringArg = V8String(GetIsolate(), stringArg);
   constexpr int argc = 1;
@@ -826,7 +890,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
@@ -842,7 +906,15 @@
 }
 
 v8::Maybe<void> V8TestCallbackInterface::customVoidMethodTestInterfaceEmptyArg(ScriptWrappable* callback_this_value, TestInterfaceEmpty* testInterfaceEmptyArg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestCallbackInterface",
+          "customVoidMethodTestInterfaceEmptyArg");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -862,7 +934,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -877,7 +949,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "customVoidMethodTestInterfaceEmptyArg"))
         .ToLocal(&value)) {
       return v8::Nothing<void>();
@@ -907,7 +979,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -915,7 +987,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_testInterfaceEmptyArg = ToV8(testInterfaceEmptyArg, argument_creation_context, GetIsolate());
   constexpr int argc = 1;
@@ -926,7 +998,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_test_legacy_callback_interface.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_test_legacy_callback_interface.cc
index 1c65ec4..82ae705b 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_test_legacy_callback_interface.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_test_legacy_callback_interface.cc
@@ -96,7 +96,15 @@
 }
 
 v8::Maybe<uint16_t> V8TestLegacyCallbackInterface::acceptNode(ScriptWrappable* callback_this_value, Node* node) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TestLegacyCallbackInterface",
+          "acceptNode");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<uint16_t>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -116,7 +124,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -131,7 +139,7 @@
     // step 9.2.2. If getResult is an abrupt completion, set completion to
     //   getResult and jump to the step labeled return.
     v8::Local<v8::Value> value;
-    if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
+    if (!CallbackObject()->Get(callback_relevant_script_state->GetContext(),
                                V8String(GetIsolate(), "acceptNode"))
         .ToLocal(&value)) {
       return v8::Nothing<uint16_t>();
@@ -161,7 +169,7 @@
     // step 2. If thisArg was not given, let thisArg be undefined.
     this_arg = v8::Undefined(GetIsolate());
   } else {
-    this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+    this_arg = ToV8(callback_this_value, callback_relevant_script_state);
   }
 
   // step: Let esArgs be the result of converting args to an ECMAScript
@@ -169,7 +177,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_node = ToV8(node, argument_creation_context, GetIsolate());
   constexpr int argc = 1;
@@ -180,7 +188,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_treat_non_object_as_null_boolean_function.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_treat_non_object_as_null_boolean_function.cc
index f5d5dee..ac079eff 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_treat_non_object_as_null_boolean_function.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_treat_non_object_as_null_boolean_function.cc
@@ -27,7 +27,15 @@
 }
 
 v8::Maybe<bool> V8TreatNonObjectAsNullBooleanFunction::Invoke(ScriptWrappable* callback_this_value) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TreatNonObjectAsNullBooleanFunction",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<bool>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -47,7 +55,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -75,7 +83,7 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
@@ -88,7 +96,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_treat_non_object_as_null_void_function.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_treat_non_object_as_null_void_function.cc
index 110a615f..004aecc 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_treat_non_object_as_null_void_function.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_treat_non_object_as_null_void_function.cc
@@ -27,7 +27,15 @@
 }
 
 v8::Maybe<void> V8TreatNonObjectAsNullVoidFunction::Invoke(ScriptWrappable* callback_this_value) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "TreatNonObjectAsNullVoidFunction",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -47,7 +55,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -64,7 +72,7 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
@@ -77,7 +85,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function.cc
index 135cbec..b7de3e0b 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function.cc
@@ -27,7 +27,15 @@
 }
 
 v8::Maybe<void> V8VoidCallbackFunction::Invoke(ScriptWrappable* callback_this_value) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "VoidCallbackFunction",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -47,7 +55,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -61,7 +69,7 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
@@ -74,7 +82,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_dictionary_arg.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_dictionary_arg.cc
index b47f3dc3..6b20056 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_dictionary_arg.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_dictionary_arg.cc
@@ -28,7 +28,15 @@
 }
 
 v8::Maybe<void> V8VoidCallbackFunctionDictionaryArg::Invoke(ScriptWrappable* callback_this_value, const TestDictionary*& arg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "VoidCallbackFunctionDictionaryArg",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -48,7 +56,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -62,14 +70,14 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_arg = ToV8(arg, argument_creation_context, GetIsolate());
   constexpr int argc = 1;
@@ -80,7 +88,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_enum_arg.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_enum_arg.cc
index 1748157..ca3d83c1 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_enum_arg.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_enum_arg.cc
@@ -28,7 +28,15 @@
 }
 
 v8::Maybe<void> V8VoidCallbackFunctionEnumArg::Invoke(ScriptWrappable* callback_this_value, const String& arg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "VoidCallbackFunctionEnumArg",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -48,7 +56,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -62,7 +70,7 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // Enum values provided by Blink must be valid, otherwise typo.
 #if DCHECK_IS_ON()
@@ -89,7 +97,7 @@
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_arg = V8String(GetIsolate(), arg);
   constexpr int argc = 1;
@@ -100,7 +108,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_interface_arg.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_interface_arg.cc
index f8f4e48..e58423e 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_interface_arg.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_interface_arg.cc
@@ -28,7 +28,15 @@
 }
 
 v8::Maybe<void> V8VoidCallbackFunctionInterfaceArg::Invoke(ScriptWrappable* callback_this_value, HTMLDivElement* divElement) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "VoidCallbackFunctionInterfaceArg",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -48,7 +56,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -62,14 +70,14 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_divElement = ToV8(divElement, argument_creation_context, GetIsolate());
   constexpr int argc = 1;
@@ -80,7 +88,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_test_interface_sequence_arg.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_test_interface_sequence_arg.cc
index ffb2273..3a3562b 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_test_interface_sequence_arg.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_test_interface_sequence_arg.cc
@@ -29,7 +29,15 @@
 }
 
 v8::Maybe<void> V8VoidCallbackFunctionTestInterfaceSequenceArg::Invoke(ScriptWrappable* callback_this_value, const HeapVector<Member<TestInterfaceImplementation>>& arg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "VoidCallbackFunctionTestInterfaceSequenceArg",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -49,7 +57,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -63,14 +71,14 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_arg = ToV8(arg, argument_creation_context, GetIsolate());
   constexpr int argc = 1;
@@ -81,7 +89,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_typedef.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_typedef.cc
index cc01356..64d2a5d 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_typedef.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_void_callback_function_typedef.cc
@@ -28,7 +28,15 @@
 }
 
 v8::Maybe<void> V8VoidCallbackFunctionTypedef::Invoke(ScriptWrappable* callback_this_value, const String& arg) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "VoidCallbackFunctionTypedef",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -48,7 +56,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -62,14 +70,14 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
   //   completion value representing the thrown exception and jump to the step
   //   labeled return.
   v8::Local<v8::Object> argument_creation_context =
-      CallbackRelevantScriptState()->GetContext()->Global();
+      callback_relevant_script_state->GetContext()->Global();
   ALLOW_UNUSED_LOCAL(argument_creation_context);
   v8::Local<v8::Value> v8_arg = V8String(GetIsolate(), arg);
   constexpr int argc = 1;
@@ -80,7 +88,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/bindings/tests/results/modules/v8_void_callback_function_modules.cc b/third_party/blink/renderer/bindings/tests/results/modules/v8_void_callback_function_modules.cc
index 8cd929d..a0260ea 100644
--- a/third_party/blink/renderer/bindings/tests/results/modules/v8_void_callback_function_modules.cc
+++ b/third_party/blink/renderer/bindings/tests/results/modules/v8_void_callback_function_modules.cc
@@ -27,7 +27,15 @@
 }
 
 v8::Maybe<void> V8VoidCallbackFunctionModules::Invoke(ScriptWrappable* callback_this_value) {
-  if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
+  ScriptState* callback_relevant_script_state =
+      CallbackRelevantScriptStateOrThrowException(
+          "VoidCallbackFunctionModules",
+          "invoke");
+  if (!callback_relevant_script_state) {
+    return v8::Nothing<void>();
+  }
+
+  if (!IsCallbackFunctionRunnable(callback_relevant_script_state,
                                   IncumbentScriptState())) {
     // Wrapper-tracing for the callback function makes the function object and
     // its creation context alive. Thus it's safe to use the creation context
@@ -47,7 +55,7 @@
 
   // step: Prepare to run script with relevant settings.
   ScriptState::Scope callback_relevant_context_scope(
-      CallbackRelevantScriptState());
+      callback_relevant_script_state);
   // step: Prepare to run a callback with stored settings.
   v8::Context::BackupIncumbentScope backup_incumbent_scope(
       IncumbentScriptState()->GetContext());
@@ -61,7 +69,7 @@
   function = CallbackFunction();
 
   v8::Local<v8::Value> this_arg;
-  this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
+  this_arg = ToV8(callback_this_value, callback_relevant_script_state);
 
   // step: Let esArgs be the result of converting args to an ECMAScript
   //   arguments list. If this throws an exception, set completion to the
@@ -74,7 +82,7 @@
   // step: Let callResult be Call(X, thisArg, esArgs).
   if (!V8ScriptRunner::CallFunction(
           function,
-          ExecutionContext::From(CallbackRelevantScriptState()),
+          ExecutionContext::From(callback_relevant_script_state),
           this_arg,
           argc,
           argv,
diff --git a/third_party/blink/renderer/modules/nfc/nfc.cc b/third_party/blink/renderer/modules/nfc/nfc.cc
index 489755b..0979b7f 100644
--- a/third_party/blink/renderer/modules/nfc/nfc.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc.cc
@@ -859,8 +859,10 @@
     auto it = callbacks_.find(id);
     if (it != callbacks_.end()) {
       V8MessageCallback* callback = it->value;
-      ScriptState* script_state = callback->CallbackRelevantScriptState();
-      DCHECK(script_state);
+      ScriptState* script_state =
+          callback->CallbackRelevantScriptStateOrReportError("NFC", "watch");
+      if (!script_state)
+        continue;
       ScriptState::Scope scope(script_state);
       const NFCMessage* nfc_message = ToNFCMessage(script_state, message);
       callback->InvokeAndReportException(nullptr, nfc_message);
diff --git a/third_party/blink/renderer/platform/bindings/callback_function_base.cc b/third_party/blink/renderer/platform/bindings/callback_function_base.cc
index 07c8a4c..12ba1af 100644
--- a/third_party/blink/renderer/platform/bindings/callback_function_base.cc
+++ b/third_party/blink/renderer/platform/bindings/callback_function_base.cc
@@ -4,18 +4,30 @@
 
 #include "third_party/blink/renderer/platform/bindings/callback_function_base.h"
 
+#include "third_party/blink/renderer/platform/bindings/binding_security_for_platform.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+
 namespace blink {
 
 CallbackFunctionBase::CallbackFunctionBase(
     v8::Local<v8::Object> callback_function) {
   DCHECK(!callback_function.IsEmpty());
 
-  callback_relevant_script_state_ =
-      ScriptState::From(callback_function->CreationContext());
-  v8::Isolate* isolate = callback_relevant_script_state_->GetIsolate();
-
+  v8::Isolate* isolate = callback_function->GetIsolate();
   callback_function_.Set(isolate, callback_function);
+
   incumbent_script_state_ = ScriptState::From(isolate->GetIncumbentContext());
+
+  // Set |callback_relevant_script_state_| iff the creation context and the
+  // incumbent context are the same origin-domain. Otherwise, leave it as
+  // nullptr.
+  v8::Local<v8::Context> creation_context =
+      callback_function->CreationContext();
+  if (BindingSecurityForPlatform::ShouldAllowAccessToV8Context(
+          incumbent_script_state_->GetContext(), creation_context,
+          BindingSecurityForPlatform::ErrorReportOption::kDoNotReport)) {
+    callback_relevant_script_state_ = ScriptState::From(creation_context);
+  }
 }
 
 void CallbackFunctionBase::Trace(Visitor* visitor) {
@@ -24,6 +36,40 @@
   visitor->Trace(incumbent_script_state_);
 }
 
+ScriptState* CallbackFunctionBase::CallbackRelevantScriptStateOrReportError(
+    const char* interface,
+    const char* operation) {
+  if (callback_relevant_script_state_)
+    return callback_relevant_script_state_;
+
+  // Report a SecurityError due to a cross origin callback object.
+  v8::TryCatch try_catch(GetIsolate());
+  try_catch.SetVerbose(true);
+  ExceptionState exception_state(
+      GetIsolate(), ExceptionState::kExecutionContext, interface, operation);
+  ScriptState::Scope incumbent_scope(incumbent_script_state_);
+  exception_state.ThrowSecurityError(
+      "An invocation of the provided callback failed due to cross origin "
+      "access.");
+  return nullptr;
+}
+
+ScriptState* CallbackFunctionBase::CallbackRelevantScriptStateOrThrowException(
+    const char* interface,
+    const char* operation) {
+  if (callback_relevant_script_state_)
+    return callback_relevant_script_state_;
+
+  // Throw a SecurityError due to a cross origin callback object.
+  ExceptionState exception_state(
+      GetIsolate(), ExceptionState::kExecutionContext, interface, operation);
+  ScriptState::Scope incumbent_scope(incumbent_script_state_);
+  exception_state.ThrowSecurityError(
+      "An invocation of the provided callback failed due to cross origin "
+      "access.");
+  return nullptr;
+}
+
 V8PersistentCallbackFunctionBase::V8PersistentCallbackFunctionBase(
     CallbackFunctionBase* callback_function)
     : callback_function_(callback_function) {
diff --git a/third_party/blink/renderer/platform/bindings/callback_function_base.h b/third_party/blink/renderer/platform/bindings/callback_function_base.h
index cc38447a..60a1494 100644
--- a/third_party/blink/renderer/platform/bindings/callback_function_base.h
+++ b/third_party/blink/renderer/platform/bindings/callback_function_base.h
@@ -36,13 +36,35 @@
   }
 
   v8::Isolate* GetIsolate() const {
-    return callback_relevant_script_state_->GetIsolate();
+    return incumbent_script_state_->GetIsolate();
   }
 
+  // Returns the ScriptState of the relevant realm of the callback object.
+  //
+  // NOTE: This function must be used only when it's pretty sure that the
+  // callcack object is the same origin-domain. Otherwise,
+  // |CallbackRelevantScriptStateOrReportError| or
+  // |CallbackRelevantScriptStateOrThrowException| must be used instead.
   ScriptState* CallbackRelevantScriptState() {
+    DCHECK(callback_relevant_script_state_);
     return callback_relevant_script_state_;
   }
 
+  // Returns the ScriptState of the relevant realm of the callback object iff
+  // the callback is the same origin-domain. Otherwise, reports an error and
+  // returns nullptr.
+  ScriptState* CallbackRelevantScriptStateOrReportError(const char* interface,
+                                                        const char* operation);
+
+  // Returns the ScriptState of the relevant realm of the callback object iff
+  // the callback is the same origin-domain. Otherwise, throws an exception and
+  // returns nullptr.
+  ScriptState* CallbackRelevantScriptStateOrThrowException(
+      const char* interface,
+      const char* operation);
+
+  DOMWrapperWorld& GetWorld() const { return incumbent_script_state_->World(); }
+
   // Returns true if the ES function has a [[Construct]] internal method.
   bool IsConstructor() const { return CallbackFunction()->IsConstructor(); }
 
@@ -52,6 +74,7 @@
   v8::Local<v8::Function> CallbackFunction() const {
     return callback_function_.NewLocal(GetIsolate()).As<v8::Function>();
   }
+
   ScriptState* IncumbentScriptState() { return incumbent_script_state_; }
 
  private:
@@ -59,7 +82,8 @@
   // Use v8::Object instead of v8::Function in order to handle
   // [TreatNonObjectAsNull].
   TraceWrapperV8Reference<v8::Object> callback_function_;
-  // The associated Realm of the callback function type value.
+  // The associated Realm of the callback function type value iff it's the same
+  // origin-domain. Otherwise, nullptr.
   Member<ScriptState> callback_relevant_script_state_;
   // The callback context, i.e. the incumbent Realm when an ECMAScript value is
   // converted to an IDL value.
diff --git a/third_party/blink/renderer/platform/bindings/callback_interface_base.cc b/third_party/blink/renderer/platform/bindings/callback_interface_base.cc
index 6523b79..73b57c5 100644
--- a/third_party/blink/renderer/platform/bindings/callback_interface_base.cc
+++ b/third_party/blink/renderer/platform/bindings/callback_interface_base.cc
@@ -31,6 +31,20 @@
   visitor->Trace(incumbent_script_state_);
 }
 
+ScriptState* CallbackInterfaceBase::CallbackRelevantScriptStateOrReportError(
+    const char* interface,
+    const char* operation) {
+  // TODO(yukishiino): Implement this function.
+  return callback_relevant_script_state_;
+}
+
+ScriptState* CallbackInterfaceBase::CallbackRelevantScriptStateOrThrowException(
+    const char* interface,
+    const char* operation) {
+  // TODO(yukishiino): Implement this function.
+  return callback_relevant_script_state_;
+}
+
 V8PersistentCallbackInterfaceBase::V8PersistentCallbackInterfaceBase(
     CallbackInterfaceBase* callback_interface)
     : callback_interface_(callback_interface) {
diff --git a/third_party/blink/renderer/platform/bindings/callback_interface_base.h b/third_party/blink/renderer/platform/bindings/callback_interface_base.h
index cdd141a..19639b63 100644
--- a/third_party/blink/renderer/platform/bindings/callback_interface_base.h
+++ b/third_party/blink/renderer/platform/bindings/callback_interface_base.h
@@ -49,14 +49,34 @@
     return callback_object_.NewLocal(GetIsolate());
   }
 
-  v8::Isolate* GetIsolate() {
-    return callback_relevant_script_state_->GetIsolate();
-  }
+  v8::Isolate* GetIsolate() { return incumbent_script_state_->GetIsolate(); }
 
+  // Returns the ScriptState of the relevant realm of the callback object.
+  //
+  // NOTE: This function must be used only when it's pretty sure that the
+  // callcack object is the same origin-domain. Otherwise,
+  // |CallbackRelevantScriptStateOrReportError| or
+  // |CallbackRelevantScriptStateOrThrowException| must be used instead.
   ScriptState* CallbackRelevantScriptState() {
+    DCHECK(callback_relevant_script_state_);
     return callback_relevant_script_state_;
   }
 
+  // Returns the ScriptState of the relevant realm of the callback object iff
+  // the callback is the same origin-domain. Otherwise, reports an error and
+  // returns nullptr.
+  ScriptState* CallbackRelevantScriptStateOrReportError(const char* interface,
+                                                        const char* operation);
+
+  // Returns the ScriptState of the relevant realm of the callback object iff
+  // the callback is the same origin-domain. Otherwise, throws an exception and
+  // returns nullptr.
+  ScriptState* CallbackRelevantScriptStateOrThrowException(
+      const char* interface,
+      const char* operation);
+
+  DOMWrapperWorld& GetWorld() const { return incumbent_script_state_->World(); }
+
   // NodeIteratorBase counts the invocation of those which are callable and
   // those which are not.
   bool IsCallbackObjectCallableForNodeIteratorBase() const {
diff --git a/third_party/blink/renderer/platform/bindings/to_v8.h b/third_party/blink/renderer/platform/bindings/to_v8.h
index 19668cac..f4e0436 100644
--- a/third_party/blink/renderer/platform/bindings/to_v8.h
+++ b/third_party/blink/renderer/platform/bindings/to_v8.h
@@ -46,10 +46,11 @@
                                  v8::Isolate* isolate) {
   // |creation_context| is intentionally ignored. Callback functions are not
   // wrappers nor clonable. ToV8 on a callback function must be used only when
-  // it's the same origin-domain in the same world.
-  DCHECK(!callback || (callback->CallbackRelevantScriptState()->GetContext() ==
-                       creation_context->CreationContext()));
-  return callback ? callback->CallbackFunction().As<v8::Value>()
+  // it's in the same world.
+  DCHECK(!callback ||
+         (&callback->GetWorld() ==
+          &ScriptState::From(creation_context->CreationContext())->World()));
+  return callback ? callback->CallbackObject().As<v8::Value>()
                   : v8::Null(isolate).As<v8::Value>();
 }