| /* |
| * 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/buildflags.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_cache_consumer.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_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/page/page.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/url_loader/cached_metadata_handler.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/event_loop.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; |
| |
| // 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, |
| ScriptState* script_state, |
| const ClassicScript& classic_script, |
| v8::ScriptOrigin origin, |
| v8::ScriptCompiler::CompileOptions compile_options, |
| v8::ScriptCompiler::NoCacheReason no_cache_reason, |
| absl::optional<inspector_compile_script_event::V8ConsumeCacheResult>* |
| cache_result) { |
| v8::Local<v8::String> code = V8String(isolate, classic_script.SourceText(), |
| classic_script.ResourceKeepAlive()); |
| |
| // TODO(kouhei): Plumb the ScriptState into this function and replace all |
| // Isolate->GetCurrentContext in this function with ScriptState->GetContext. |
| if (ScriptStreamer* streamer = classic_script.Streamer()) { |
| if (v8::ScriptCompiler::StreamedSource* source = |
| streamer->Source(v8::ScriptType::kClassic)) { |
| // Final compile call for a streamed compilation. |
| // Streaming compilation may involve use of code cache. |
| // TODO(leszeks): Add compile timer to streaming compilation. |
| return v8::ScriptCompiler::Compile(script_state->GetContext(), source, |
| code, origin); |
| } |
| } |
| |
| // Allow inspector to use its own compilation cache store. |
| v8::ScriptCompiler::CachedData* inspector_data = nullptr; |
| // The probe below allows inspector to either inject the cached code |
| // or override compile_options to force eager compilation of code |
| // when producing the cache. |
| probe::ApplyCompilationModeOverride(ExecutionContext::From(script_state), |
| classic_script, &inspector_data, |
| &compile_options); |
| if (inspector_data) { |
| v8::ScriptCompiler::Source source(code, origin, inspector_data); |
| v8::MaybeLocal<v8::Script> script = |
| v8::ScriptCompiler::Compile(script_state->GetContext(), &source, |
| v8::ScriptCompiler::kConsumeCodeCache); |
| return script; |
| } |
| |
| switch (compile_options) { |
| case v8::ScriptCompiler::kNoCompileOptions: |
| case v8::ScriptCompiler::kEagerCompile: |
| case v8::ScriptCompiler::kProduceCompileHints: { |
| v8::ScriptCompiler::Source source(code, origin); |
| return v8::ScriptCompiler::Compile(script_state->GetContext(), &source, |
| compile_options, no_cache_reason); |
| } |
| |
| case v8::ScriptCompiler::kConsumeCodeCache: { |
| // Compile a script, and consume a V8 cache that was generated previously. |
| CachedMetadataHandler* cache_handler = classic_script.CacheHandler(); |
| ScriptCacheConsumer* cache_consumer = classic_script.CacheConsumer(); |
| scoped_refptr<CachedMetadata> cached_metadata = |
| V8CodeCache::GetCachedMetadata(cache_handler); |
| v8::ScriptCompiler::Source source( |
| code, origin, |
| V8CodeCache::CreateCachedData(cached_metadata).release(), |
| cache_consumer |
| ? cache_consumer->TakeV8ConsumeTask(cached_metadata.get()) |
| : nullptr); |
| const v8::ScriptCompiler::CachedData* cached_data = |
| source.GetCachedData(); |
| v8::MaybeLocal<v8::Script> script = |
| v8::ScriptCompiler::Compile(script_state->GetContext(), &source, |
| v8::ScriptCompiler::kConsumeCodeCache); |
| cache_handler->DidUseCodeCache(); |
| // The ScriptState has an associated context. We expect the current |
| // context to match the context associated with Script context when |
| // compiling the script for main world. Hence it is safe to use the |
| // CodeCacheHost corresponding to the script execution context. For |
| // isolated world (for ex: extension scripts), the current context |
| // may not match the script context. Though currently code caching is |
| // disabled for extensions. |
| if (cached_data->rejected) { |
| cache_handler->ClearCachedMetadata( |
| ExecutionContext::GetCodeCacheHostFromContext( |
| ExecutionContext::From(script_state)), |
| CachedMetadataHandler::kClearPersistentStorage); |
| } |
| if (cache_result) { |
| *cache_result = absl::make_optional( |
| inspector_compile_script_event::V8ConsumeCacheResult( |
| cached_data->length, cached_data->rejected)); |
| } |
| return script; |
| } |
| default: |
| NOTREACHED(); |
| } |
| |
| // 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 ClassicScript& classic_script, |
| v8::ScriptOrigin origin, |
| v8::ScriptCompiler::CompileOptions compile_options, |
| v8::ScriptCompiler::NoCacheReason no_cache_reason) { |
| v8::Isolate* isolate = script_state->GetIsolate(); |
| if (classic_script.SourceText().length() >= v8::String::kMaxLength) { |
| V8ThrowException::ThrowError(isolate, "Source file too large."); |
| return v8::Local<v8::Script>(); |
| } |
| |
| const String& file_name = classic_script.SourceUrl(); |
| const TextPosition& script_start_position = classic_script.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()); |
| |
| if (!*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(kTraceEventCategoryGroup)) { |
| return CompileScriptInternal(isolate, script_state, classic_script, origin, |
| compile_options, no_cache_reason, nullptr); |
| } |
| |
| absl::optional<inspector_compile_script_event::V8ConsumeCacheResult> |
| cache_result; |
| v8::MaybeLocal<v8::Script> script = |
| CompileScriptInternal(isolate, script_state, classic_script, origin, |
| compile_options, no_cache_reason, &cache_result); |
| TRACE_EVENT_END1( |
| kTraceEventCategoryGroup, "v8.compile", "data", |
| [&](perfetto::TracedValue context) { |
| inspector_compile_script_event::Data( |
| std::move(context), file_name, script_start_position, cache_result, |
| compile_options == v8::ScriptCompiler::kEagerCompile, |
| classic_script.Streamer(), classic_script.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( |
| isolate, 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, params.SourceURL())); |
| |
| v8::Local<v8::String> code = V8String(isolate, params.GetSourceText()); |
| absl::optional<inspector_compile_script_event::V8ConsumeCacheResult> |
| 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. |
| script = v8::ScriptCompiler::CompileModule( |
| isolate->GetCurrentContext(), streamer->Source(v8::ScriptType::kModule), |
| code, origin); |
| } else { |
| switch (compile_options) { |
| case v8::ScriptCompiler::kNoCompileOptions: |
| case v8::ScriptCompiler::kEagerCompile: |
| case v8::ScriptCompiler::kProduceCompileHints: { |
| 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. |
| CachedMetadataHandler* cache_handler = params.CacheHandler(); |
| DCHECK(cache_handler); |
| cache_handler->DidUseCodeCache(); |
| // TODO(leszeks): Add support for passing in ScriptCacheConsumer. |
| v8::ScriptCompiler::Source source( |
| code, origin, |
| V8CodeCache::CreateCachedData(cache_handler).release()); |
| const v8::ScriptCompiler::CachedData* cached_data = |
| source.GetCachedData(); |
| script = v8::ScriptCompiler::CompileModule( |
| isolate, &source, compile_options, no_cache_reason); |
| // The ScriptState also has an associated context. We expect the current |
| // context to match the context associated with Script context when |
| // compiling the module. Hence it is safe to use the CodeCacheHost |
| // corresponding to the current execution context. |
| ExecutionContext* execution_context = |
| ExecutionContext::From(isolate->GetCurrentContext()); |
| if (cached_data->rejected) { |
| cache_handler->ClearCachedMetadata( |
| ExecutionContext::GetCodeCacheHostFromContext(execution_context), |
| CachedMetadataHandler::kClearPersistentStorage); |
| } |
| cache_result = absl::make_optional( |
| inspector_compile_script_event::V8ConsumeCacheResult( |
| cached_data->length, cached_data->rejected)); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| TRACE_EVENT_END1(kTraceEventCategoryGroup, "v8.compileModule", "data", |
| [&](perfetto::TracedValue context) { |
| inspector_compile_script_event::Data( |
| std::move(context), file_name, start_position, |
| cache_result, |
| compile_options == v8::ScriptCompiler::kEagerCompile, |
| streamer, params.NotStreamingReason()); |
| }); |
| return script; |
| } |
| |
| v8::MaybeLocal<v8::Value> V8ScriptRunner::RunCompiledScript( |
| v8::Isolate* isolate, |
| v8::Local<v8::Script> script, |
| v8::Local<v8::Data> host_defined_options, |
| ExecutionContext* context) { |
| DCHECK(!script.IsEmpty()); |
| |
| 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>(); |
| } |
| if (RuntimeEnabledFeatures::BlinkLifecycleScriptForbiddenEnabled()) { |
| CHECK(!ScriptForbiddenScope::WillBeScriptForbidden()); |
| } else { |
| DCHECK(!ScriptForbiddenScope::WillBeScriptForbidden()); |
| } |
| |
| 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, isolate->GetCurrentContext(), |
| ToCoreString(script_url), |
| script->GetUnboundScript()->GetId()); |
| result = script->Run(isolate->GetCurrentContext(), host_defined_options); |
| } |
| |
| CHECK(!isolate->IsDead()); |
| return result; |
| } |
| |
| namespace { |
| void DelayedProduceCodeCacheTask(ScriptState* script_state, |
| v8::Global<v8::Script> script, |
| CachedMetadataHandler* cache_handler, |
| size_t source_text_length, |
| KURL source_url, |
| TextPosition source_start_position) { |
| if (!script_state->ContextIsValid()) |
| return; |
| ScriptState::Scope scope(script_state); |
| v8::Isolate* isolate = script_state->GetIsolate(); |
| ExecutionContext* execution_context = ExecutionContext::From(script_state); |
| V8CodeCache::ProduceCache( |
| isolate, ExecutionContext::GetCodeCacheHostFromContext(execution_context), |
| script.Get(isolate), cache_handler, source_text_length, source_url, |
| source_start_position, |
| V8CodeCache::ProduceCacheOptions::kProduceCodeCache); |
| } |
| } // namespace |
| |
| 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 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()); |
| |
| DEVTOOLS_TIMELINE_TRACE_EVENT( |
| "EvaluateScript", inspector_evaluate_script_event::Data, frame, |
| classic_script->SourceUrl().GetString(), classic_script->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); |
| } |
| |
| v8::Local<v8::Script> script; |
| |
| CachedMetadataHandler* cache_handler = classic_script->CacheHandler(); |
| if (cache_handler) { |
| cache_handler->Check( |
| ExecutionContext::GetCodeCacheHostFromContext(execution_context), |
| classic_script->SourceText()); |
| } |
| v8::ScriptCompiler::CompileOptions compile_options; |
| V8CodeCache::ProduceCacheOptions produce_cache_options; |
| v8::ScriptCompiler::NoCacheReason no_cache_reason; |
| Page* page = frame != nullptr ? frame->GetPage() : nullptr; |
| bool might_generate_compile_hints = |
| page ? page->GetV8CrowdsourcedCompileHintsProducer().MightGenerateData() |
| : false; |
| |
| std::tie(compile_options, produce_cache_options, no_cache_reason) = |
| V8CodeCache::GetCompileOptions(execution_context->GetV8CacheOptions(), |
| *classic_script, |
| might_generate_compile_hints); |
| |
| v8::ScriptOrigin origin = classic_script->CreateScriptOrigin(isolate); |
| v8::MaybeLocal<v8::Value> maybe_result; |
| if (V8ScriptRunner::CompileScript(script_state, *classic_script, origin, |
| compile_options, no_cache_reason) |
| .ToLocal(&script)) { |
| maybe_result = V8ScriptRunner::RunCompiledScript( |
| isolate, script, origin.GetHostDefinedOptions(), execution_context); |
| probe::DidProduceCompilationCache( |
| probe::ToCoreProbeSink(execution_context), *classic_script, script); |
| |
| // The ScriptState has an associated context. We expect the current |
| // context to match the context associated with Script context when |
| // compiling the script in the main world. Hence it is safe to use the |
| // CodeCacheHost corresponding to the script execution context. For |
| // isolated world the contexts may not match. Though code caching is |
| // disabled for extensions so it is OK to use execution_context here. |
| |
| if (produce_cache_options == |
| V8CodeCache::ProduceCacheOptions::kProduceCodeCache && |
| cache_handler) { |
| cache_handler->WillProduceCodeCache(); |
| } |
| if (produce_cache_options == |
| V8CodeCache::ProduceCacheOptions::kProduceCodeCache && |
| base::FeatureList::IsEnabled(features::kCacheCodeOnIdle) && |
| (features::kCacheCodeOnIdleDelayServiceWorkerOnlyParam.Get() |
| ? execution_context->IsServiceWorkerGlobalScope() |
| : true)) { |
| auto delay = |
| base::Milliseconds(features::kCacheCodeOnIdleDelayParam.Get()); |
| // Workers don't have a concept of idle tasks, so use a default task for |
| // these. |
| TaskType task_type = |
| frame ? TaskType::kIdleTask : TaskType::kInternalDefault; |
| execution_context->GetTaskRunner(task_type)->PostDelayedTask( |
| FROM_HERE, |
| WTF::BindOnce(&DelayedProduceCodeCacheTask, |
| // TODO(leszeks): Consider passing the |
| // script state as a weak persistent. |
| WrapPersistent(script_state), |
| v8::Global<v8::Script>(isolate, script), |
| WrapPersistent(cache_handler), |
| classic_script->SourceText().length(), |
| classic_script->SourceUrl(), |
| classic_script->StartPosition()), |
| delay); |
| } else { |
| V8CodeCache::ProduceCache( |
| isolate, |
| ExecutionContext::GetCodeCacheHostFromContext(execution_context), |
| script, cache_handler, classic_script->SourceText().length(), |
| classic_script->SourceUrl(), classic_script->StartPosition(), |
| produce_cache_options); |
| } |
| |
| #if BUILDFLAG(ENABLE_V8_COMPILE_HINTS) |
| if (page != nullptr) { |
| if (compile_options == v8::ScriptCompiler::kProduceCompileHints) { |
| // TODO(chromium:1406506): Add a compile hints solution for workers. |
| // TODO(chromium:1406506): Add a compile hints solution for fenced |
| // frames. |
| // TODO(chromium:1406506): Add a compile hints solution for |
| // out-of-process iframes. |
| page->GetV8CrowdsourcedCompileHintsProducer().RecordScript( |
| frame, execution_context, script, script_state); |
| } |
| } |
| #endif // BUILDFLAG(ENABLE_V8_COMPILE_HINTS) |
| } |
| |
| // 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::FromClassicExceptionRethrown(); |
| } |
| |
| // Step 8.1.3. Otherwise, rethrow errors is false. Perform the following |
| // steps: [spec text] |
| if (!rethrow_errors.ShouldRethrow()) { |
| // #report-the-error for rethrow errors == true is already handled via |
| // `TryCatch::SetVerbose(true)` above. |
| return ScriptEvaluationResult::FromClassicException( |
| try_catch.Exception()); |
| } |
| } |
| // |v8::TryCatch| is (and should be) exited, before ThrowException() below. |
| |
| // kDoNotSanitize case is processed and early-exited above. |
| DCHECK(rethrow_errors.ShouldRethrow()); |
| 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::FromClassicExceptionRethrown(); |
| } |
| |
| 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>(); |
| } |
| if (RuntimeEnabledFeatures::BlinkLifecycleScriptForbiddenEnabled()) { |
| CHECK(!ScriptForbiddenScope::WillBeScriptForbidden()); |
| } else { |
| DCHECK(!ScriptForbiddenScope::WillBeScriptForbidden()); |
| } |
| |
| // 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, isolate->GetCurrentContext(), function, |
| depth); |
| |
| if (!depth) { |
| TRACE_EVENT_BEGIN1("devtools.timeline", "FunctionCall", "data", |
| [&](perfetto::TracedValue ctx) { |
| inspector_function_call_event::Data(std::move(ctx), |
| 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> argv[], |
| v8::Isolate* isolate) { |
| LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(context); |
| 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>(); |
| } |
| if (RuntimeEnabledFeatures::BlinkLifecycleScriptForbiddenEnabled()) { |
| CHECK(!ScriptForbiddenScope::WillBeScriptForbidden()); |
| } else { |
| DCHECK(!ScriptForbiddenScope::WillBeScriptForbidden()); |
| } |
| |
| DCHECK(!window || !window->GetFrame() || |
| BindingSecurity::ShouldAllowAccessTo( |
| ToLocalDOMWindow(function->GetCreationContextChecked()), window, |
| 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", |
| [&](perfetto::TracedValue trace_context) { |
| inspector_function_call_event::Data( |
| std::move(trace_context), context, function); |
| }); |
| } |
| |
| probe::CallFunction probe(context, isolate->GetCurrentContext(), function, |
| depth); |
| v8::MaybeLocal<v8::Value> result = |
| function->Call(isolate->GetCurrentContext(), receiver, argc, argv); |
| CHECK(!isolate->IsDead()); |
| |
| if (!depth) |
| TRACE_EVENT_END0("devtools.timeline", "FunctionCall"); |
| |
| return result; |
| } |
| |
| class ModuleEvaluationRejectionCallback final |
| : public ScriptFunction::Callable { |
| public: |
| ModuleEvaluationRejectionCallback() = default; |
| |
| ScriptValue Call(ScriptState* script_state, ScriptValue value) override { |
| ModuleRecord::ReportException(script_state, 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, script_state->GetContext(), |
| 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(); |
| |
| DCHECK(!try_catch.HasCaught()); |
| result = ScriptEvaluationResult::FromModuleSuccess( |
| maybe_result.ToLocalChecked()); |
| |
| // <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::BindOnce(&Modulator::ProduceCacheModuleTreeTopLevel, |
| WrapWeakPersistent(Modulator::From(script_state)), |
| WrapWeakPersistent(module_script))); |
| } |
| |
| if (!rethrow_errors.ShouldRethrow()) { |
| // <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 = |
| MakeGarbageCollected<ScriptFunction>( |
| script_state, |
| MakeGarbageCollected<ModuleEvaluationRejectionCallback>()) |
| ->V8Function(); |
| // 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); |
| } |
| |
| // <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 |