blob: 29f65c22b3b229c455419cd80e9102a968515bf1 [file] [log] [blame]
/*
* 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_script_runner.h"
#include "base/feature_list.h"
#include "build/build_config.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/v8_cache_options.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/binding_security.h"
#include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
#include "third_party/blink/renderer/bindings/core/v8/script_evaluation_result.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
#include "third_party/blink/renderer/bindings/core/v8/script_streamer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_code_cache.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.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/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/script/classic_script.h"
#include "third_party/blink/renderer/core/script/modulator.h"
#include "third_party/blink/renderer/core/script/module_script.h"
#include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.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/loader/fetch/cached_metadata.h"
#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata_handler.h"
#include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
namespace blink {
namespace {
// Used to throw an exception before we exceed the C++ stack and crash.
// This limit was arrived at arbitrarily. crbug.com/449744
const int kMaxRecursionDepth = 44;
bool InDiscardExperiment() {
return base::FeatureList::IsEnabled(
blink::features::kDiscardCodeCacheAfterFirstUse);
}
// In order to make sure all pending messages to be processed in
// v8::Function::Call, we don't call throwStackOverflowException
// directly. Instead, we create a v8::Function of
// throwStackOverflowException and call it.
void ThrowStackOverflowException(
const v8::FunctionCallbackInfo<v8::Value>& info) {
V8ThrowException::ThrowRangeError(info.GetIsolate(),
"Maximum call stack size exceeded.");
}
void ThrowScriptForbiddenException(v8::Isolate* isolate) {
V8ThrowException::ThrowError(isolate, "Script execution is forbidden.");
}
v8::MaybeLocal<v8::Value> ThrowStackOverflowExceptionIfNeeded(
v8::Isolate* isolate,
v8::MicrotaskQueue* microtask_queue) {
if (V8PerIsolateData::From(isolate)->IsHandlingRecursionLevelError()) {
// If we are already handling a recursion level error, we should
// not invoke v8::Function::Call.
return v8::Undefined(isolate);
}
v8::MicrotasksScope microtasks_scope(
isolate, microtask_queue, v8::MicrotasksScope::kDoNotRunMicrotasks);
V8PerIsolateData::From(isolate)->SetIsHandlingRecursionLevelError(true);
ScriptForbiddenScope::AllowUserAgentScript allow_script;
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::MaybeLocal<v8::Value> result =
v8::Function::New(context, ThrowStackOverflowException,
v8::Local<v8::Value>(), 0,
v8::ConstructorBehavior::kThrow)
.ToLocalChecked()
->Call(context, v8::Undefined(isolate), 0, nullptr);
V8PerIsolateData::From(isolate)->SetIsHandlingRecursionLevelError(false);
return result;
}
v8::MaybeLocal<v8::Script> CompileScriptInternal(
v8::Isolate* isolate,
ExecutionContext* execution_context,
const ScriptSourceCode& source_code,
v8::ScriptOrigin origin,
v8::ScriptCompiler::CompileOptions compile_options,
v8::ScriptCompiler::NoCacheReason no_cache_reason,
inspector_compile_script_event::V8CacheResult* cache_result) {
v8::Local<v8::String> code = V8String(isolate, source_code.Source());
if (ScriptStreamer* streamer = source_code.Streamer()) {
// Final compile call for a streamed compilation.
// Streaming compilation may involve use of code cache.
// TODO(leszeks): Add compile timer to streaming compilation.
DCHECK(streamer->IsFinished());
DCHECK(!streamer->IsStreamingSuppressed());
return v8::ScriptCompiler::Compile(isolate->GetCurrentContext(),
streamer->Source(), code, origin);
}
// Allow inspector to use its own compilation cache store.
v8::ScriptCompiler::CachedData* inspector_data = nullptr;
probe::ConsumeCompilationCache(execution_context, source_code,
&inspector_data);
if (inspector_data) {
v8::ScriptCompiler::Source source(code, origin, inspector_data);
v8::MaybeLocal<v8::Script> script =
v8::ScriptCompiler::Compile(isolate->GetCurrentContext(), &source,
v8::ScriptCompiler::kConsumeCodeCache);
return script;
}
switch (compile_options) {
case v8::ScriptCompiler::kNoCompileOptions:
case v8::ScriptCompiler::kEagerCompile: {
v8::ScriptCompiler::Source source(code, origin);
return v8::ScriptCompiler::Compile(isolate->GetCurrentContext(), &source,
compile_options, no_cache_reason);
}
case v8::ScriptCompiler::kConsumeCodeCache: {
// Compile a script, and consume a V8 cache that was generated previously.
SingleCachedMetadataHandler* cache_handler = source_code.CacheHandler();
v8::ScriptCompiler::CachedData* cached_data =
V8CodeCache::CreateCachedData(cache_handler);
v8::ScriptCompiler::Source source(code, origin, cached_data);
v8::MaybeLocal<v8::Script> script =
v8::ScriptCompiler::Compile(isolate->GetCurrentContext(), &source,
v8::ScriptCompiler::kConsumeCodeCache);
if (cached_data->rejected) {
cache_handler->ClearCachedMetadata(
CachedMetadataHandler::kClearPersistentStorage);
} else if (InDiscardExperiment()) {
// Experimentally free code cache from memory after first use. See
// http://crbug.com/1045052.
cache_handler->ClearCachedMetadata(
CachedMetadataHandler::kDiscardLocally);
}
if (cache_result) {
cache_result->consume_result = base::make_optional(
inspector_compile_script_event::V8CacheResult::ConsumeResult(
v8::ScriptCompiler::kConsumeCodeCache, cached_data->length,
cached_data->rejected));
}
return script;
}
}
// All switch branches should return and we should never get here.
// But some compilers aren't sure, hence this default.
NOTREACHED();
return v8::MaybeLocal<v8::Script>();
}
int GetMicrotasksScopeDepth(v8::Isolate* isolate,
v8::MicrotaskQueue* microtask_queue) {
if (microtask_queue)
return microtask_queue->GetMicrotasksScopeDepth();
return v8::MicrotasksScope::GetCurrentDepth(isolate);
}
} // namespace
v8::MaybeLocal<v8::Script> V8ScriptRunner::CompileScript(
ScriptState* script_state,
const ScriptSourceCode& source,
SanitizeScriptErrors sanitize_script_errors,
v8::ScriptCompiler::CompileOptions compile_options,
v8::ScriptCompiler::NoCacheReason no_cache_reason,
const ReferrerScriptInfo& referrer_info) {
v8::Isolate* isolate = script_state->GetIsolate();
if (source.Source().length() >= v8::String::kMaxLength) {
V8ThrowException::ThrowError(isolate, "Source file too large.");
return v8::Local<v8::Script>();
}
const String& file_name = source.Url();
const TextPosition& script_start_position = source.StartPosition();
constexpr const char* kTraceEventCategoryGroup = "v8,devtools.timeline";
TRACE_EVENT_BEGIN1(kTraceEventCategoryGroup, "v8.compile", "fileName",
file_name.Utf8());
ExecutionContext* execution_context = ExecutionContext::From(script_state);
probe::V8Compile probe(execution_context, file_name,
script_start_position.line_.ZeroBasedInt(),
script_start_position.column_.ZeroBasedInt());
// NOTE: For compatibility with WebCore, ScriptSourceCode's line starts at
// 1, whereas v8 starts at 0.
v8::ScriptOrigin origin(
V8String(isolate, file_name), script_start_position.line_.ZeroBasedInt(),
script_start_position.column_.ZeroBasedInt(),
sanitize_script_errors == SanitizeScriptErrors::kDoNotSanitize, -1,
V8String(isolate, source.SourceMapUrl()),
sanitize_script_errors == SanitizeScriptErrors::kSanitize,
false, // is_wasm
false, // is_module
referrer_info.ToV8HostDefinedOptions(isolate));
if (!*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(kTraceEventCategoryGroup)) {
return CompileScriptInternal(isolate, execution_context, source, origin,
compile_options, no_cache_reason, nullptr);
}
inspector_compile_script_event::V8CacheResult cache_result;
v8::MaybeLocal<v8::Script> script =
CompileScriptInternal(isolate, execution_context, source, origin,
compile_options, no_cache_reason, &cache_result);
TRACE_EVENT_END1(kTraceEventCategoryGroup, "v8.compile", "data",
inspector_compile_script_event::Data(
file_name, script_start_position, cache_result,
source.Streamer(), source.NotStreamingReason()));
return script;
}
v8::MaybeLocal<v8::Module> V8ScriptRunner::CompileModule(
v8::Isolate* isolate,
const ModuleScriptCreationParams& params,
const TextPosition& start_position,
v8::ScriptCompiler::CompileOptions compile_options,
v8::ScriptCompiler::NoCacheReason no_cache_reason,
const ReferrerScriptInfo& referrer_info) {
const String file_name = params.SourceURL();
constexpr const char* kTraceEventCategoryGroup = "v8,devtools.timeline";
TRACE_EVENT_BEGIN1(kTraceEventCategoryGroup, "v8.compileModule", "fileName",
file_name.Utf8());
// |resource_is_shared_cross_origin| is always true and |resource_is_opaque|
// is always false because CORS is enforced to module scripts.
v8::ScriptOrigin origin(V8String(isolate, file_name),
start_position.line_.ZeroBasedInt(),
start_position.column_.ZeroBasedInt(),
true, // resource_is_shared_cross_origin
-1, // script id
v8::String::Empty(isolate), // source_map_url
false, // resource_is_opaque
false, // is_wasm
true, // is_module
referrer_info.ToV8HostDefinedOptions(isolate));
v8::Local<v8::String> code = V8String(isolate, params.GetSourceText());
inspector_compile_script_event::V8CacheResult cache_result;
v8::MaybeLocal<v8::Module> script;
ScriptStreamer* streamer = params.GetScriptStreamer();
if (streamer) {
// Final compile call for a streamed compilation.
// Streaming compilation may involve use of code cache.
// TODO(leszeks): Add compile timer to streaming compilation.
DCHECK(streamer->IsFinished());
DCHECK(!streamer->IsStreamingSuppressed());
script = v8::ScriptCompiler::CompileModule(
isolate->GetCurrentContext(), streamer->Source(), code, origin);
} else {
switch (compile_options) {
case v8::ScriptCompiler::kNoCompileOptions:
case v8::ScriptCompiler::kEagerCompile: {
v8::ScriptCompiler::Source source(code, origin);
script = v8::ScriptCompiler::CompileModule(
isolate, &source, compile_options, no_cache_reason);
break;
}
case v8::ScriptCompiler::kConsumeCodeCache: {
// Compile a script, and consume a V8 cache that was generated
// previously.
SingleCachedMetadataHandler* cache_handler = params.CacheHandler();
DCHECK(cache_handler);
v8::ScriptCompiler::CachedData* cached_data =
V8CodeCache::CreateCachedData(cache_handler);
v8::ScriptCompiler::Source source(code, origin, cached_data);
script = v8::ScriptCompiler::CompileModule(
isolate, &source, compile_options, no_cache_reason);
if (cached_data->rejected) {
cache_handler->ClearCachedMetadata(
CachedMetadataHandler::kClearPersistentStorage);
} else if (InDiscardExperiment()) {
// Experimentally free code cache from memory after first use. See
// http://crbug.com/1045052.
cache_handler->ClearCachedMetadata(
CachedMetadataHandler::kDiscardLocally);
}
cache_result.consume_result = base::make_optional(
inspector_compile_script_event::V8CacheResult::ConsumeResult(
compile_options, cached_data->length, cached_data->rejected));
break;
}
}
}
TRACE_EVENT_END1(kTraceEventCategoryGroup, "v8.compileModule", "data",
inspector_compile_script_event::Data(
file_name, start_position, cache_result, streamer,
params.NotStreamingReason()));
return script;
}
v8::MaybeLocal<v8::Value> V8ScriptRunner::RunCompiledScript(
v8::Isolate* isolate,
v8::Local<v8::Script> script,
ExecutionContext* context) {
DCHECK(!script.IsEmpty());
LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(context);
ScopedFrameBlamer frame_blamer(window ? window->GetFrame() : nullptr);
v8::Local<v8::Value> script_name =
script->GetUnboundScript()->GetScriptName();
TRACE_EVENT1("v8", "v8.run", "fileName",
TRACE_STR_COPY(*v8::String::Utf8Value(isolate, script_name)));
RuntimeCallStatsScopedTracer rcs_scoped_tracer(isolate);
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kV8);
v8::MicrotaskQueue* microtask_queue = ToMicrotaskQueue(context);
if (GetMicrotasksScopeDepth(isolate, microtask_queue) > kMaxRecursionDepth)
return ThrowStackOverflowExceptionIfNeeded(isolate, microtask_queue);
CHECK(!context->ContextLifecycleObserverSet().IsIteratingOverObservers());
// Run the script and keep track of the current recursion depth.
v8::MaybeLocal<v8::Value> result;
{
if (ScriptForbiddenScope::IsScriptForbidden()) {
ThrowScriptForbiddenException(isolate);
return v8::MaybeLocal<v8::Value>();
}
v8::Isolate::SafeForTerminationScope safe_for_termination(isolate);
v8::MicrotasksScope microtasks_scope(isolate, microtask_queue,
v8::MicrotasksScope::kRunMicrotasks);
v8::Local<v8::String> script_url;
if (!script_name->ToString(isolate->GetCurrentContext())
.ToLocal(&script_url))
return result;
// ToCoreString here should be zero copy due to externalized string
// unpacked.
probe::ExecuteScript probe(context, ToCoreString(script_url),
script->GetUnboundScript()->GetId());
result = script->Run(isolate->GetCurrentContext());
}
CHECK(!isolate->IsDead());
return result;
}
ScriptEvaluationResult V8ScriptRunner::CompileAndRunScript(
ScriptState* script_state,
ClassicScript* classic_script,
ExecuteScriptPolicy policy,
RethrowErrorsOption rethrow_errors) {
if (!script_state)
return ScriptEvaluationResult::FromClassicNotRun();
// |script_state->GetContext()| must be initialized here already, typically
// due to a WindowProxy() call inside ToScriptState*() that is used to get the
// ScriptState.
ExecutionContext* execution_context = ExecutionContext::From(script_state);
DCHECK(execution_context->IsContextThread());
if (policy == ExecuteScriptPolicy::kDoNotExecuteScriptWhenScriptsDisabled &&
!execution_context->CanExecuteScripts(kAboutToExecuteScript)) {
return ScriptEvaluationResult::FromClassicNotRun();
}
v8::Isolate* isolate = script_state->GetIsolate();
const ScriptSourceCode& source = classic_script->GetScriptSourceCode();
const SanitizeScriptErrors sanitize_script_errors =
classic_script->GetSanitizeScriptErrors();
LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(execution_context);
WorkerOrWorkletGlobalScope* worker_or_worklet_global_scope =
DynamicTo<WorkerOrWorkletGlobalScope>(execution_context);
LocalFrame* frame = window ? window->GetFrame() : nullptr;
if (window && window->document()->IsInitialEmptyDocument()) {
window->GetFrame()->Loader().DidAccessInitialDocument();
} else if (worker_or_worklet_global_scope) {
DCHECK_EQ(
script_state,
worker_or_worklet_global_scope->ScriptController()->GetScriptState());
DCHECK(worker_or_worklet_global_scope->ScriptController()
->IsContextInitialized());
DCHECK(worker_or_worklet_global_scope->ScriptController()
->IsReadyToEvaluate());
}
v8::Context::Scope scope(script_state->GetContext());
TRACE_EVENT1("devtools.timeline", "EvaluateScript", "data",
inspector_evaluate_script_event::Data(
frame, source.Url().GetString(), source.StartPosition()));
// Scope for |v8::TryCatch|.
{
v8::TryCatch try_catch(isolate);
// Step 8.3. Otherwise, rethrow errors is false. Perform the following
// steps: [spec text]
// Step 8.3.1. Report the exception given by evaluationStatus.[[Value]]
// for script. [spec text]
//
// This will be done inside V8 by setting TryCatch::SetVerbose(true) here.
if (!rethrow_errors.ShouldRethrow()) {
try_catch.SetVerbose(true);
}
// Omit storing base URL if it is same as source URL.
// Note: This improves chance of getting into a fast path in
// ReferrerScriptInfo::ToV8HostDefinedOptions.
const KURL base_url = classic_script->BaseURL();
KURL stored_base_url = (base_url == source.Url()) ? KURL() : base_url;
// TODO(hiroshige): Remove this code and related use counters once the
// measurement is done.
ReferrerScriptInfo::BaseUrlSource base_url_source =
ReferrerScriptInfo::BaseUrlSource::kOther;
if (source.SourceLocationType() ==
ScriptSourceLocationType::kExternalFile &&
!base_url.IsNull()) {
switch (sanitize_script_errors) {
case SanitizeScriptErrors::kDoNotSanitize:
base_url_source =
ReferrerScriptInfo::BaseUrlSource::kClassicScriptCORSSameOrigin;
break;
case SanitizeScriptErrors::kSanitize:
base_url_source =
ReferrerScriptInfo::BaseUrlSource::kClassicScriptCORSCrossOrigin;
break;
}
}
const ReferrerScriptInfo referrer_info(
stored_base_url, classic_script->FetchOptions(), base_url_source);
v8::Local<v8::Script> script;
v8::ScriptCompiler::CompileOptions compile_options;
V8CodeCache::ProduceCacheOptions produce_cache_options;
v8::ScriptCompiler::NoCacheReason no_cache_reason;
std::tie(compile_options, produce_cache_options, no_cache_reason) =
V8CodeCache::GetCompileOptions(execution_context->GetV8CacheOptions(),
source);
v8::MaybeLocal<v8::Value> maybe_result;
if (V8ScriptRunner::CompileScript(script_state, source,
sanitize_script_errors, compile_options,
no_cache_reason, referrer_info)
.ToLocal(&script)) {
maybe_result =
V8ScriptRunner::RunCompiledScript(isolate, script, execution_context);
probe::ProduceCompilationCache(probe::ToCoreProbeSink(execution_context),
source, script);
V8CodeCache::ProduceCache(isolate, script, source, produce_cache_options);
}
// TODO(crbug/1114601): Investigate whether to check CanContinue() in other
// script evaluation code paths.
if (!try_catch.CanContinue()) {
if (worker_or_worklet_global_scope)
worker_or_worklet_global_scope->ScriptController()->ForbidExecution();
return ScriptEvaluationResult::FromClassicAborted();
}
if (!try_catch.HasCaught()) {
// Step 10. If evaluationStatus is a normal completion, then return
// evaluationStatus. [spec text]
v8::Local<v8::Value> result;
bool success = maybe_result.ToLocal(&result);
DCHECK(success);
return ScriptEvaluationResult::FromClassicSuccess(result);
}
DCHECK(maybe_result.IsEmpty());
if (rethrow_errors.ShouldRethrow() &&
sanitize_script_errors == SanitizeScriptErrors::kDoNotSanitize) {
// Step 8.1. If rethrow errors is true and script's muted errors is
// false, then: [spec text]
//
// Step 8.1.2. Rethrow evaluationStatus.[[Value]]. [spec text]
//
// We rethrow exceptions reported from importScripts() here. The
// original filename/lineno/colno information (which points inside of
// imported scripts) is kept through ReThrow(), and will be eventually
// reported to WorkerGlobalScope.onerror via `TryCatch::SetVerbose(true)`
// called at top-level worker script evaluation.
try_catch.ReThrow();
return ScriptEvaluationResult::FromClassicException();
}
}
// |v8::TryCatch| is (and should be) exited, before ThrowException() below.
if (rethrow_errors.ShouldRethrow()) {
// kDoNotSanitize case is processed and early-exited above.
DCHECK_EQ(sanitize_script_errors, SanitizeScriptErrors::kSanitize);
// Step 8.2. If rethrow errors is true and script's muted errors is
// true, then: [spec text]
//
// Step 8.2.2. Throw a "NetworkError" DOMException. [spec text]
//
// We don't supply any message here to avoid leaking details of muted
// errors.
V8ThrowException::ThrowException(
isolate, V8ThrowDOMException::CreateOrEmpty(
isolate, DOMExceptionCode::kNetworkError,
rethrow_errors.Message()));
return ScriptEvaluationResult::FromClassicException();
}
// #report-the-error for rethrow errors == true is already handled via
// |TryCatch::SetVerbose(true)| above.
return ScriptEvaluationResult::FromClassicException();
}
v8::MaybeLocal<v8::Value> V8ScriptRunner::CompileAndRunInternalScript(
v8::Isolate* isolate,
ScriptState* script_state,
const ScriptSourceCode& source_code) {
DCHECK_EQ(isolate, script_state->GetIsolate());
v8::ScriptCompiler::CompileOptions compile_options;
V8CodeCache::ProduceCacheOptions produce_cache_options;
v8::ScriptCompiler::NoCacheReason no_cache_reason;
std::tie(compile_options, produce_cache_options, no_cache_reason) =
V8CodeCache::GetCompileOptions(mojom::blink::V8CacheOptions::kDefault,
source_code);
// Currently internal scripts don't have cache handlers. So we should not
// produce cache for them.
DCHECK_EQ(produce_cache_options,
V8CodeCache::ProduceCacheOptions::kNoProduceCache);
v8::Local<v8::Script> script;
// Use default ScriptReferrerInfo here:
// - nonce: empty for internal script, and
// - parser_state: always "not parser inserted" for internal scripts.
if (!V8ScriptRunner::CompileScript(
script_state, source_code, SanitizeScriptErrors::kDoNotSanitize,
compile_options, no_cache_reason, ReferrerScriptInfo())
.ToLocal(&script))
return v8::MaybeLocal<v8::Value>();
TRACE_EVENT0("v8", "v8.run");
RuntimeCallStatsScopedTracer rcs_scoped_tracer(isolate);
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kV8);
v8::Isolate::SafeForTerminationScope safe_for_termination(isolate);
v8::MicrotasksScope microtasks_scope(
isolate, ToMicrotaskQueue(script_state),
v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::MaybeLocal<v8::Value> result = script->Run(isolate->GetCurrentContext());
CHECK(!isolate->IsDead());
return result;
}
v8::MaybeLocal<v8::Value> V8ScriptRunner::CallAsConstructor(
v8::Isolate* isolate,
v8::Local<v8::Object> constructor,
ExecutionContext* context,
int argc,
v8::Local<v8::Value> argv[]) {
TRACE_EVENT0("v8", "v8.callAsConstructor");
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kV8);
v8::MicrotaskQueue* microtask_queue = ToMicrotaskQueue(context);
int depth = GetMicrotasksScopeDepth(isolate, microtask_queue);
if (depth >= kMaxRecursionDepth)
return ThrowStackOverflowExceptionIfNeeded(isolate, microtask_queue);
CHECK(!context->ContextLifecycleObserverSet().IsIteratingOverObservers());
if (ScriptForbiddenScope::IsScriptForbidden()) {
ThrowScriptForbiddenException(isolate);
return v8::MaybeLocal<v8::Value>();
}
// TODO(dominicc): When inspector supports tracing object
// invocation, change this to use v8::Object instead of
// v8::Function. All callers use functions because
// CustomElementRegistry#define's IDL signature is Function.
CHECK(constructor->IsFunction());
v8::Local<v8::Function> function = constructor.As<v8::Function>();
v8::Isolate::SafeForTerminationScope safe_for_termination(isolate);
v8::MicrotasksScope microtasks_scope(isolate, ToMicrotaskQueue(context),
v8::MicrotasksScope::kRunMicrotasks);
probe::CallFunction probe(context, function, depth);
if (!depth) {
TRACE_EVENT_BEGIN1("devtools.timeline", "FunctionCall", "data",
inspector_function_call_event::Data(context, function));
}
v8::MaybeLocal<v8::Value> result =
constructor->CallAsConstructor(isolate->GetCurrentContext(), argc, argv);
CHECK(!isolate->IsDead());
if (!depth)
TRACE_EVENT_END0("devtools.timeline", "FunctionCall");
return result;
}
v8::MaybeLocal<v8::Value> V8ScriptRunner::CallFunction(
v8::Local<v8::Function> function,
ExecutionContext* context,
v8::Local<v8::Value> receiver,
int argc,
v8::Local<v8::Value> args[],
v8::Isolate* isolate) {
LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(context);
LocalFrame* frame = window ? window->GetFrame() : nullptr;
ScopedFrameBlamer frame_blamer(frame);
TRACE_EVENT0("v8", "v8.callFunction");
RuntimeCallStatsScopedTracer rcs_scoped_tracer(isolate);
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kV8);
v8::MicrotaskQueue* microtask_queue = ToMicrotaskQueue(context);
int depth = GetMicrotasksScopeDepth(isolate, microtask_queue);
if (depth >= kMaxRecursionDepth)
return ThrowStackOverflowExceptionIfNeeded(isolate, microtask_queue);
CHECK(!context->ContextLifecycleObserverSet().IsIteratingOverObservers());
if (ScriptForbiddenScope::IsScriptForbidden()) {
ThrowScriptForbiddenException(isolate);
return v8::MaybeLocal<v8::Value>();
}
DCHECK(!frame || BindingSecurity::ShouldAllowAccessToFrame(
ToLocalDOMWindow(function->CreationContext()), frame,
BindingSecurity::ErrorReportOption::kDoNotReport));
v8::Isolate::SafeForTerminationScope safe_for_termination(isolate);
v8::MicrotasksScope microtasks_scope(isolate, microtask_queue,
v8::MicrotasksScope::kRunMicrotasks);
if (!depth) {
TRACE_EVENT_BEGIN1("devtools.timeline", "FunctionCall", "data",
inspector_function_call_event::Data(context, function));
}
probe::CallFunction probe(context, function, depth);
v8::MaybeLocal<v8::Value> result =
function->Call(isolate->GetCurrentContext(), receiver, argc, args);
CHECK(!isolate->IsDead());
if (!depth)
TRACE_EVENT_END0("devtools.timeline", "FunctionCall");
return result;
}
class ModuleEvaluationRejectionCallback final : public ScriptFunction {
public:
explicit ModuleEvaluationRejectionCallback(ScriptState* script_state)
: ScriptFunction(script_state) {}
static v8::Local<v8::Function> CreateFunction(ScriptState* script_state) {
ModuleEvaluationRejectionCallback* self =
MakeGarbageCollected<ModuleEvaluationRejectionCallback>(script_state);
return self->BindToV8Function();
}
private:
ScriptValue Call(ScriptValue value) override {
ModuleRecord::ReportException(GetScriptState(), value.V8Value());
return ScriptValue();
}
};
// <specdef href="https://html.spec.whatwg.org/C/#run-a-module-script">
// Spec with TLA: https://github.com/whatwg/html/pull/4352
ScriptEvaluationResult V8ScriptRunner::EvaluateModule(
ModuleScript* module_script,
RethrowErrorsOption rethrow_errors) {
// <spec step="1">If rethrow errors is not given, let it be false.</spec>
// <spec step="2">Let settings be the settings object of script.</spec>
//
// The settings object is |module_script->SettingsObject()|.
ScriptState* script_state = module_script->SettingsObject()->GetScriptState();
DCHECK_EQ(Modulator::From(script_state), module_script->SettingsObject());
ExecutionContext* execution_context = ExecutionContext::From(script_state);
v8::Isolate* isolate = script_state->GetIsolate();
// TODO(crbug.com/1151165): Ideally v8::Context should be entered before
// CanExecuteScripts().
v8::Context::Scope scope(script_state->GetContext());
// <spec step="3">Check if we can run script with settings. If this returns
// "do not run" then return NormalCompletion(empty).</spec>
if (!execution_context->CanExecuteScripts(kAboutToExecuteScript)) {
return ScriptEvaluationResult::FromModuleNotRun();
}
// <spec step="4">Prepare to run script given settings.</spec>
//
// These are placed here to also cover ModuleRecord::ReportException().
v8::MicrotasksScope microtasks_scope(isolate,
ToMicrotaskQueue(execution_context),
v8::MicrotasksScope::kRunMicrotasks);
// Without TLA: <spec step="5">Let evaluationStatus be null.</spec>
ScriptEvaluationResult result = ScriptEvaluationResult::FromModuleNotRun();
// <spec step="6">If script's error to rethrow is not null, ...</spec>
if (module_script->HasErrorToRethrow()) {
// Without TLA: <spec step="6">... then set evaluationStatus to Completion
// { [[Type]]: throw, [[Value]]: script's error to rethrow,
// [[Target]]: empty }.</spec>
// With TLA: <spec step="5">If script's error to rethrow is not null,
// then let valuationPromise be a promise rejected with script's error
// to rethrow.</spec>
result = ScriptEvaluationResult::FromModuleException(
module_script->CreateErrorToRethrow().V8Value());
} else {
// <spec step="7">Otherwise:</spec>
// <spec step="7.1">Let record be script's record.</spec>
v8::Local<v8::Module> record = module_script->V8Module();
CHECK(!record.IsEmpty());
// <spec step="7.2">Set evaluationStatus to record.Evaluate(). ...</spec>
// Isolate exceptions that occur when executing the code. These exceptions
// should not interfere with javascript code we might evaluate from C++
// when returning from here.
v8::TryCatch try_catch(isolate);
// Script IDs are not available on errored modules or on non-source text
// modules, so we give them a default value.
probe::ExecuteScript probe(execution_context, module_script->SourceURL(),
record->GetStatus() != v8::Module::kErrored &&
record->IsSourceTextModule()
? record->ScriptId()
: v8::UnboundScript::kNoScriptId);
TRACE_EVENT0("v8,devtools.timeline", "v8.evaluateModule");
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kV8);
v8::Isolate::SafeForTerminationScope safe_for_termination(isolate);
// Do not perform a microtask checkpoint here. A checkpoint is performed
// only after module error handling to ensure proper timing with and
// without top-level await.
v8::MaybeLocal<v8::Value> maybe_result =
record->Evaluate(script_state->GetContext());
if (!try_catch.CanContinue())
return ScriptEvaluationResult::FromModuleAborted();
v8::Local<v8::Value> v8_result;
if (!maybe_result.ToLocal(&v8_result)) {
DCHECK(try_catch.HasCaught());
result =
ScriptEvaluationResult::FromModuleException(try_catch.Exception());
} else {
DCHECK(!try_catch.HasCaught());
result = ScriptEvaluationResult::FromModuleSuccess(v8_result);
}
// <spec step="7.2">... If Evaluate fails to complete as a result of the
// user agent aborting the running script, then set evaluationStatus to
// Completion { [[Type]]: throw, [[Value]]: a new "QuotaExceededError"
// DOMException, [[Target]]: empty }.</spec>
}
// [not specced] Store V8 code cache on successful evaluation.
if (result.GetResultType() == ScriptEvaluationResult::ResultType::kSuccess) {
execution_context->GetTaskRunner(TaskType::kNetworking)
->PostTask(FROM_HERE,
WTF::Bind(&Modulator::ProduceCacheModuleTreeTopLevel,
WrapWeakPersistent(Modulator::From(script_state)),
WrapWeakPersistent(module_script)));
}
if (!rethrow_errors.ShouldRethrow()) {
if (base::FeatureList::IsEnabled(features::kTopLevelAwait)) {
// <spec step="7"> If report errors is true, then upon rejection of
// evaluationPromise with reason, report the exception given by reason
// for script.</spec>
v8::Local<v8::Function> callback_failure =
ModuleEvaluationRejectionCallback::CreateFunction(script_state);
// Add a rejection handler to report back errors once the result
// promise is rejected.
result.GetPromise(script_state)
.Then(v8::Local<v8::Function>(), callback_failure);
} else {
// <spec step="8">If evaluationStatus is an abrupt completion,
// then:</spec>
if (result.GetResultType() ==
ScriptEvaluationResult::ResultType::kException) {
// <spec step="8.2">Otherwise, report the exception given by
// evaluationStatus.[[Value]] for script.</spec>
ModuleRecord::ReportException(script_state,
result.GetExceptionForModule());
}
}
}
// <spec step="8">Clean up after running script with settings.</spec>
// Partially implemented in MicrotaskScope destructor and the
// v8::Context::Scope destructor.
return result;
}
void V8ScriptRunner::ReportException(v8::Isolate* isolate,
v8::Local<v8::Value> exception) {
DCHECK(!exception.IsEmpty());
// https://html.spec.whatwg.org/C/#report-the-error
v8::Local<v8::Message> message =
v8::Exception::CreateMessage(isolate, exception);
if (IsMainThread())
V8Initializer::MessageHandlerInMainThread(message, exception);
else
V8Initializer::MessageHandlerInWorker(message, exception);
}
} // namespace blink