blob: 0707116fa43d92a1dc5202bb5a8b0c8f05214025 [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_initializer.h"
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_macros.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/binding_security.h"
#include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
#include "third_party/blink/renderer/bindings/core/v8/rejected_promises.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/bindings/core/v8/use_counter_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_context_snapshot.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_error_event.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_idle_task_runner.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/inspector/main_thread_debugger.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/script/modulator.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h"
#include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/loader/fetch/access_control_status.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_violation_reporting_policy.h"
#include "third_party/blink/renderer/platform/wtf/address_sanitizer.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/stack_util.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.h"
#include "v8/include/v8-profiler.h"
#include "v8/include/v8.h"
namespace blink {
static void ReportFatalErrorInMainThread(const char* location,
const char* message) {
DVLOG(1) << "V8 error: " << message << " (" << location << ").";
LOG(FATAL);
}
static void ReportOOMErrorInMainThread(const char* location, bool is_js_heap) {
DVLOG(1) << "V8 " << (is_js_heap ? "javascript" : "process") << " OOM: ("
<< location << ").";
OOM_CRASH();
}
namespace {
// Set to BloatedRendererDetector::OnNearV8HeapLimitOnMainThread during startup.
static NearV8HeapLimitCallback g_near_heap_limit_on_main_thread_callback_ =
nullptr;
void Record(NearV8HeapLimitHandling handling,
v8::Isolate* isolate,
size_t heap_limit,
ukm::UkmRecorder* ukm_recorder,
int64_t ukm_source_id) {
UMA_HISTOGRAM_ENUMERATION("BloatedRenderer.V8.NearV8HeapLimitHandling",
handling);
if (ukm_recorder) {
// Record size metrics in MB similar to Memory.Experimental.Renderer2.V8.
const size_t kMB = 1024 * 1024;
v8::HeapStatistics heap_statistics;
isolate->GetHeapStatistics(&heap_statistics);
ukm::builders::BloatedRenderer(ukm_source_id)
.SetV8_NearV8HeapLimitHandling(static_cast<int64_t>(handling))
.SetV8_Heap(heap_statistics.total_physical_size() / kMB)
.SetV8_Heap_AllocatedObjects(heap_statistics.used_heap_size() / kMB)
.SetV8_Heap_Limit(heap_limit / kMB)
.Record(ukm_recorder);
}
}
size_t IncreaseV8HeapLimit(size_t v8_heap_limit) {
// The heap limit for a bloated page should be increased to avoid immediate
// OOM crash. The exact amount is not important, it should be sufficiently
// large to give enough time for the browser process to reload the page.
// Increase the heap limit by 25%.
return v8_heap_limit + v8_heap_limit / 4;
}
size_t NearHeapLimitCallbackOnMainThread(void* data,
size_t current_heap_limit,
size_t initial_heap_limit) {
v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(data);
V8PerIsolateData* per_isolate_data = V8PerIsolateData::From(isolate);
if (per_isolate_data->IsNearV8HeapLimitHandled()) {
// Ignore all calls after the first one.
return current_heap_limit;
}
per_isolate_data->HandledNearV8HeapLimit();
// Find the main document for UKM recording.
Document* document = nullptr;
int pages = 0;
for (Page* page : Page::OrdinaryPages()) {
if (page->MainFrame()->IsLocalFrame()) {
++pages;
document = ToLocalFrame(page->MainFrame())->GetDocument();
}
}
ukm::UkmRecorder* ukm_recorder = nullptr;
int64_t ukm_source_id = 0;
// Do not record UKM if there are multiple pages as we cannot attribute
// the heap size to a specific page.
if (pages == 1 && document) {
ukm_recorder = document->UkmRecorder();
ukm_source_id = document->UkmSourceID();
}
if (current_heap_limit != initial_heap_limit) {
Record(NearV8HeapLimitHandling::kIgnoredDueToChangedHeapLimit, isolate,
current_heap_limit, ukm_recorder, ukm_source_id);
return current_heap_limit;
}
NearV8HeapLimitHandling handling =
g_near_heap_limit_on_main_thread_callback_();
Record(handling, isolate, current_heap_limit, ukm_recorder, ukm_source_id);
return (handling == NearV8HeapLimitHandling::kForwardedToBrowser)
? IncreaseV8HeapLimit(current_heap_limit)
: current_heap_limit;
}
size_t NearHeapLimitCallbackOnWorkerThread(void* data,
size_t current_heap_limit,
size_t initial_heap_limit) {
v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(data);
V8PerIsolateData* per_isolate_data = V8PerIsolateData::From(isolate);
if (per_isolate_data->IsNearV8HeapLimitHandled()) {
// Ignore all calls after the first one.
return current_heap_limit;
}
per_isolate_data->HandledNearV8HeapLimit();
// Do not record UKM on worker thread.
Record(NearV8HeapLimitHandling::kIgnoredDueToWorker, isolate,
current_heap_limit, nullptr, 0);
return current_heap_limit;
}
} // anonymous namespace
void V8Initializer::SetNearV8HeapLimitOnMainThreadCallback(
NearV8HeapLimitCallback callback) {
g_near_heap_limit_on_main_thread_callback_ = callback;
}
static String ExtractMessageForConsole(v8::Isolate* isolate,
v8::Local<v8::Value> data) {
if (V8DOMWrapper::IsWrapper(isolate, data)) {
v8::Local<v8::Object> obj = v8::Local<v8::Object>::Cast(data);
const WrapperTypeInfo* type = ToWrapperTypeInfo(obj);
if (V8DOMException::wrapperTypeInfo.IsSubclass(type)) {
DOMException* exception = V8DOMException::ToImpl(obj);
if (exception && !exception->MessageForConsole().IsEmpty())
return exception->ToStringForConsole();
}
}
return g_empty_string;
}
namespace {
MessageLevel MessageLevelFromNonFatalErrorLevel(int error_level) {
MessageLevel level = kErrorMessageLevel;
switch (error_level) {
case v8::Isolate::kMessageDebug:
level = kVerboseMessageLevel;
break;
case v8::Isolate::kMessageLog:
case v8::Isolate::kMessageInfo:
level = kInfoMessageLevel;
break;
case v8::Isolate::kMessageWarning:
level = kWarningMessageLevel;
break;
case v8::Isolate::kMessageError:
level = kInfoMessageLevel;
break;
default:
NOTREACHED();
}
return level;
}
// NOTE: when editing this, please also edit the error messages we throw when
// the size is exceeded (see uses of the constant), which use the human-friendly
// "4KB" text.
const size_t kWasmWireBytesLimit = 1 << 12;
} // namespace
void V8Initializer::MessageHandlerInMainThread(v8::Local<v8::Message> message,
v8::Local<v8::Value> data) {
DCHECK(IsMainThread());
v8::Isolate* isolate = v8::Isolate::GetCurrent();
if (isolate->GetEnteredContext().IsEmpty())
return;
// If called during context initialization, there will be no entered context.
ScriptState* script_state = ScriptState::Current(isolate);
if (!script_state->ContextIsValid())
return;
ExecutionContext* context = ExecutionContext::From(script_state);
std::unique_ptr<SourceLocation> location =
SourceLocation::FromMessage(isolate, message, context);
if (message->ErrorLevel() != v8::Isolate::kMessageError) {
context->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource,
MessageLevelFromNonFatalErrorLevel(message->ErrorLevel()),
ToCoreStringWithNullCheck(message->Get()), std::move(location)));
return;
}
AccessControlStatus access_control_status =
message->IsSharedCrossOrigin() ? kSharableCrossOrigin : kOpaqueResource;
ErrorEvent* event = ErrorEvent::Create(
ToCoreStringWithNullCheck(message->Get()), std::move(location),
ScriptValue::From(script_state, data), &script_state->World());
String message_for_console = ExtractMessageForConsole(isolate, data);
if (!message_for_console.IsEmpty())
event->SetUnsanitizedMessage("Uncaught " + message_for_console);
context->DispatchErrorEvent(event, access_control_status);
}
void V8Initializer::MessageHandlerInWorker(v8::Local<v8::Message> message,
v8::Local<v8::Value> data) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
V8PerIsolateData* per_isolate_data = V8PerIsolateData::From(isolate);
// During the frame teardown, there may not be a valid context.
ScriptState* script_state = ScriptState::Current(isolate);
if (!script_state->ContextIsValid())
return;
// Exceptions that occur in error handler should be ignored since in that case
// WorkerGlobalScope::dispatchErrorEvent will send the exception to the worker
// object.
if (per_isolate_data->IsReportingException())
return;
per_isolate_data->SetReportingException(true);
ExecutionContext* context = ExecutionContext::From(script_state);
std::unique_ptr<SourceLocation> location =
SourceLocation::FromMessage(isolate, message, context);
if (message->ErrorLevel() != v8::Isolate::kMessageError) {
context->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource,
MessageLevelFromNonFatalErrorLevel(message->ErrorLevel()),
ToCoreStringWithNullCheck(message->Get()), std::move(location)));
return;
}
ErrorEvent* event = ErrorEvent::Create(
ToCoreStringWithNullCheck(message->Get()), std::move(location),
ScriptValue::From(script_state, data), &script_state->World());
AccessControlStatus access_control_status =
message->IsSharedCrossOrigin() ? kSharableCrossOrigin : kOpaqueResource;
// If execution termination has been triggered as part of constructing
// the error event from the v8::Message, quietly leave.
if (!isolate->IsExecutionTerminating()) {
ExecutionContext::From(script_state)
->DispatchErrorEvent(event, access_control_status);
}
per_isolate_data->SetReportingException(false);
}
namespace {
static RejectedPromises& RejectedPromisesOnMainThread() {
DCHECK(IsMainThread());
DEFINE_STATIC_LOCAL(scoped_refptr<RejectedPromises>, rejected_promises,
(RejectedPromises::Create()));
return *rejected_promises;
}
} // namespace
void V8Initializer::ReportRejectedPromisesOnMainThread() {
RejectedPromisesOnMainThread().ProcessQueue();
}
static void PromiseRejectHandler(v8::PromiseRejectMessage data,
RejectedPromises& rejected_promises,
ScriptState* script_state) {
if (data.GetEvent() == v8::kPromiseHandlerAddedAfterReject) {
rejected_promises.HandlerAdded(data);
return;
} else if (data.GetEvent() == v8::kPromiseRejectAfterResolved ||
data.GetEvent() == v8::kPromiseResolveAfterResolved) {
// Ignore reject/resolve after resolved.
return;
}
DCHECK_EQ(v8::kPromiseRejectWithNoHandler, data.GetEvent());
v8::Isolate* isolate = script_state->GetIsolate();
ExecutionContext* context = ExecutionContext::From(script_state);
v8::Local<v8::Value> exception = data.GetValue();
if (V8DOMWrapper::IsWrapper(isolate, exception)) {
// Try to get the stack & location from a wrapped exception object (e.g.
// DOMException).
DCHECK(exception->IsObject());
auto private_error = V8PrivateProperty::GetDOMExceptionError(isolate);
v8::Local<v8::Value> error;
if (private_error.GetOrUndefined(exception.As<v8::Object>())
.ToLocal(&error) &&
!error->IsUndefined()) {
exception = error;
}
}
String error_message;
AccessControlStatus cors_status = kOpaqueResource;
std::unique_ptr<SourceLocation> location;
v8::Local<v8::Message> message =
v8::Exception::CreateMessage(isolate, exception);
if (!message.IsEmpty()) {
// message->Get() can be empty here. https://crbug.com/450330
error_message = ToCoreStringWithNullCheck(message->Get());
location = SourceLocation::FromMessage(isolate, message, context);
if (message->IsSharedCrossOrigin())
cors_status = kSharableCrossOrigin;
} else {
location =
SourceLocation::Create(context->Url().GetString(), 0, 0, nullptr);
}
String message_for_console =
ExtractMessageForConsole(isolate, data.GetValue());
if (!message_for_console.IsEmpty())
error_message = "Uncaught " + message_for_console;
rejected_promises.RejectedWithNoHandler(script_state, data, error_message,
std::move(location), cors_status);
}
static void PromiseRejectHandlerInMainThread(v8::PromiseRejectMessage data) {
DCHECK(IsMainThread());
v8::Local<v8::Promise> promise = data.GetPromise();
v8::Isolate* isolate = promise->GetIsolate();
// TODO(ikilpatrick): Remove this check, extensions tests that use
// extensions::ModuleSystemTest incorrectly don't have a valid script state.
LocalDOMWindow* window = CurrentDOMWindow(isolate);
if (!window || !window->IsCurrentlyDisplayedInFrame())
return;
// Bail out if called during context initialization.
ScriptState* script_state = ScriptState::Current(isolate);
if (!script_state->ContextIsValid())
return;
PromiseRejectHandler(data, RejectedPromisesOnMainThread(), script_state);
}
static void PromiseRejectHandlerInWorker(v8::PromiseRejectMessage data) {
v8::Local<v8::Promise> promise = data.GetPromise();
// Bail out if called during context initialization.
v8::Isolate* isolate = promise->GetIsolate();
ScriptState* script_state = ScriptState::Current(isolate);
if (!script_state->ContextIsValid())
return;
ExecutionContext* execution_context = ExecutionContext::From(script_state);
if (!execution_context)
return;
auto* script_controller =
To<WorkerGlobalScope>(execution_context)->ScriptController();
DCHECK(script_controller);
PromiseRejectHandler(data, *script_controller->GetRejectedPromises(),
script_state);
}
static void FailedAccessCheckCallbackInMainThread(v8::Local<v8::Object> holder,
v8::AccessType type,
v8::Local<v8::Value> data) {
// FIXME: We should modify V8 to pass in more contextual information (context,
// property, and object).
BindingSecurity::FailedAccessCheckFor(v8::Isolate::GetCurrent(),
WrapperTypeInfo::Unwrap(data), holder);
}
static bool CodeGenerationCheckCallbackInMainThread(
v8::Local<v8::Context> context,
v8::Local<v8::String> source) {
if (ExecutionContext* execution_context = ToExecutionContext(context)) {
DCHECK(execution_context->IsDocument() ||
execution_context->IsMainThreadWorkletGlobalScope());
if (ContentSecurityPolicy* policy =
execution_context->GetContentSecurityPolicy()) {
v8::String::Value source_str(context->GetIsolate(), source);
UChar snippet[ContentSecurityPolicy::kMaxSampleLength + 1];
size_t len = std::min((sizeof(snippet) / sizeof(UChar)) - 1,
static_cast<size_t>(source_str.length()));
memcpy(snippet, *source_str, len * sizeof(UChar));
snippet[len] = 0;
return policy->AllowEval(
ScriptState::From(context), SecurityViolationReportingPolicy::kReport,
ContentSecurityPolicy::kWillThrowException, snippet);
}
}
return false;
}
static bool WasmCodeGenerationCheckCallbackInMainThread(
v8::Local<v8::Context> context,
v8::Local<v8::String> source) {
if (ExecutionContext* execution_context = ToExecutionContext(context)) {
if (ContentSecurityPolicy* policy =
To<Document>(execution_context)->GetContentSecurityPolicy()) {
v8::String::Value source_str(context->GetIsolate(), source);
UChar snippet[ContentSecurityPolicy::kMaxSampleLength + 1];
size_t len = std::min((sizeof(snippet) / sizeof(UChar)) - 1,
static_cast<size_t>(source_str.length()));
memcpy(snippet, *source_str, len * sizeof(UChar));
snippet[len] = 0;
// Wasm code generation is allowed if we have either the wasm-eval
// directive or the unsafe-eval directive. However, we only recognize
// wasm-eval for certain schemes
return policy->AllowWasmEval(ScriptState::From(context),
SecurityViolationReportingPolicy::kReport,
ContentSecurityPolicy::kWillThrowException,
snippet) ||
policy->AllowEval(ScriptState::From(context),
SecurityViolationReportingPolicy::kReport,
ContentSecurityPolicy::kWillThrowException,
snippet);
}
}
return false;
}
static bool WasmThreadsEnabledCallback(v8::Local<v8::Context> context) {
ExecutionContext* execution_context = ToExecutionContext(context);
if (!execution_context)
return false;
return OriginTrials::WebAssemblyThreadsEnabled(execution_context);
}
v8::Local<v8::Value> NewRangeException(v8::Isolate* isolate,
const char* message) {
return v8::Exception::RangeError(
v8::String::NewFromOneByte(isolate,
reinterpret_cast<const uint8_t*>(message),
v8::NewStringType::kNormal)
.ToLocalChecked());
}
void ThrowRangeException(v8::Isolate* isolate, const char* message) {
isolate->ThrowException(NewRangeException(isolate, message));
}
static bool WasmModuleOverride(
const v8::FunctionCallbackInfo<v8::Value>& args) {
// Return false if we want the base behavior to proceed.
if (!WTF::IsMainThread() || args.Length() < 1)
return false;
v8::Local<v8::Value> source = args[0];
if ((source->IsArrayBuffer() &&
v8::Local<v8::ArrayBuffer>::Cast(source)->ByteLength() >
kWasmWireBytesLimit) ||
(source->IsArrayBufferView() &&
v8::Local<v8::ArrayBufferView>::Cast(source)->ByteLength() >
kWasmWireBytesLimit)) {
ThrowRangeException(args.GetIsolate(),
"WebAssembly.Compile is disallowed on the main thread, "
"if the buffer size is larger than 4KB. Use "
"WebAssembly.compile, or compile on a worker thread.");
// Return true because we injected new behavior and we do not
// want the default behavior.
return true;
}
return false;
}
static bool WasmInstanceOverride(
const v8::FunctionCallbackInfo<v8::Value>& args) {
// Return false if we want the base behavior to proceed.
if (!WTF::IsMainThread() || args.Length() < 1)
return false;
v8::Local<v8::Value> source = args[0];
if (!source->IsWebAssemblyCompiledModule())
return false;
v8::Local<v8::WasmCompiledModule> module =
v8::Local<v8::WasmCompiledModule>::Cast(source);
if (module->GetWasmWireBytesRef().size > kWasmWireBytesLimit) {
ThrowRangeException(
args.GetIsolate(),
"WebAssembly.Instance is disallowed on the main thread, "
"if the buffer size is larger than 4KB. Use "
"WebAssembly.instantiate.");
return true;
}
return false;
}
static v8::MaybeLocal<v8::Promise> HostImportModuleDynamically(
v8::Local<v8::Context> context,
v8::Local<v8::ScriptOrModule> v8_referrer,
v8::Local<v8::String> v8_specifier) {
ScriptState* script_state = ScriptState::From(context);
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
Modulator* modulator = Modulator::From(script_state);
if (!modulator) {
resolver->Reject();
return v8::Local<v8::Promise>::Cast(promise.V8Value());
}
String specifier = ToCoreStringWithNullCheck(v8_specifier);
v8::Local<v8::Value> v8_referrer_resource_url =
v8_referrer->GetResourceName();
KURL referrer_resource_url;
if (v8_referrer_resource_url->IsString()) {
String referrer_resource_url_str =
ToCoreString(v8::Local<v8::String>::Cast(v8_referrer_resource_url));
if (!referrer_resource_url_str.IsEmpty())
referrer_resource_url = KURL(NullURL(), referrer_resource_url_str);
}
ReferrerScriptInfo referrer_info =
ReferrerScriptInfo::FromV8HostDefinedOptions(
context, v8_referrer->GetHostDefinedOptions());
modulator->ResolveDynamically(specifier, referrer_resource_url, referrer_info,
resolver);
return v8::Local<v8::Promise>::Cast(promise.V8Value());
}
// https://html.spec.whatwg.org/#hostgetimportmetaproperties
static void HostGetImportMetaProperties(v8::Local<v8::Context> context,
v8::Local<v8::Module> module,
v8::Local<v8::Object> meta) {
ScriptState* script_state = ScriptState::From(context);
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
Modulator* modulator = Modulator::From(script_state);
if (!modulator)
return;
// TODO(shivanisha): Can a valid source url be passed to the constructor.
ModuleImportMeta host_meta = modulator->HostGetImportMetaProperties(
ScriptModule(isolate, module, KURL()));
// 3. Return <<Record { [[Key]]: "url", [[Value]]: urlString }>>. [spec text]
v8::Local<v8::String> url_key = V8String(isolate, "url");
v8::Local<v8::String> url_value = V8String(isolate, host_meta.Url());
meta->CreateDataProperty(context, url_key, url_value).ToChecked();
}
static void InitializeV8Common(v8::Isolate* isolate) {
isolate->AddGCPrologueCallback(V8GCController::GcPrologue);
isolate->AddGCEpilogueCallback(V8GCController::GcEpilogue);
isolate->SetEmbedderHeapTracer(
RuntimeEnabledFeatures::HeapUnifiedGarbageCollectionEnabled()
? static_cast<v8::EmbedderHeapTracer*>(
V8PerIsolateData::From(isolate)->GetUnifiedHeapController())
: static_cast<v8::EmbedderHeapTracer*>(
V8PerIsolateData::From(isolate)
->GetScriptWrappableMarkingVisitor()));
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kScoped);
isolate->SetUseCounterCallback(&UseCounterCallback);
isolate->SetWasmModuleCallback(WasmModuleOverride);
isolate->SetWasmInstanceCallback(WasmInstanceOverride);
isolate->SetWasmThreadsEnabledCallback(WasmThreadsEnabledCallback);
isolate->SetHostImportModuleDynamicallyCallback(HostImportModuleDynamically);
isolate->SetHostInitializeImportMetaObjectCallback(
HostGetImportMetaProperties);
V8ContextSnapshot::EnsureInterfaceTemplates(isolate);
WasmResponseExtensions::Initialize(isolate);
}
namespace {
class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
// Allocate() methods return null to signal allocation failure to V8, which
// should respond by throwing a RangeError, per
// http://www.ecma-international.org/ecma-262/6.0/#sec-createbytedatablock.
void* Allocate(size_t size) override {
return WTF::ArrayBufferContents::AllocateMemoryOrNull(
size, WTF::ArrayBufferContents::kZeroInitialize);
}
void* AllocateUninitialized(size_t size) override {
return WTF::ArrayBufferContents::AllocateMemoryOrNull(
size, WTF::ArrayBufferContents::kDontInitialize);
}
void Free(void* data, size_t size) override {
WTF::ArrayBufferContents::FreeMemory(data);
}
};
} // namespace
static void AdjustAmountOfExternalAllocatedMemory(int64_t diff) {
#if DCHECK_IS_ON()
static int64_t process_total = 0;
DEFINE_THREAD_SAFE_STATIC_LOCAL(Mutex, mutex, ());
{
MutexLocker locker(mutex);
process_total += diff;
DCHECK_GE(process_total, 0)
<< "total amount = " << process_total << ", diff = " << diff;
}
#endif
v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(diff);
}
void V8Initializer::InitializeMainThread(const intptr_t* reference_table) {
DCHECK(IsMainThread());
WTF::ArrayBufferContents::Initialize(AdjustAmountOfExternalAllocatedMemory);
DEFINE_STATIC_LOCAL(ArrayBufferAllocator, array_buffer_allocator, ());
auto v8_extras_mode = RuntimeEnabledFeatures::ExperimentalV8ExtrasEnabled()
? gin::IsolateHolder::kStableAndExperimentalV8Extras
: gin::IsolateHolder::kStableV8Extras;
gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode,
v8_extras_mode, &array_buffer_allocator,
reference_table);
ThreadScheduler* scheduler = ThreadScheduler::Current();
#if defined(USE_V8_CONTEXT_SNAPSHOT)
V8PerIsolateData::V8ContextSnapshotMode v8_context_snapshot_mode =
Platform::Current()->IsTakingV8ContextSnapshot()
? V8PerIsolateData::V8ContextSnapshotMode::kTakeSnapshot
: V8PerIsolateData::V8ContextSnapshotMode::kUseSnapshot;
if (v8_context_snapshot_mode ==
V8PerIsolateData::V8ContextSnapshotMode::kUseSnapshot &&
!RuntimeEnabledFeatures::V8ContextSnapshotEnabled()) {
v8_context_snapshot_mode =
V8PerIsolateData::V8ContextSnapshotMode::kDontUseSnapshot;
}
#else
V8PerIsolateData::V8ContextSnapshotMode v8_context_snapshot_mode =
V8PerIsolateData::V8ContextSnapshotMode::kDontUseSnapshot;
#endif // USE_V8_CONTEXT_SNAPSHOT
v8::Isolate* isolate = V8PerIsolateData::Initialize(scheduler->V8TaskRunner(),
v8_context_snapshot_mode);
// ThreadState::isolate_ needs to be set before setting the EmbedderHeapTracer
// as setting the tracer indicates that a V8 garbage collection should trace
// over to Blink.
DCHECK(ThreadState::MainThreadState());
if (RuntimeEnabledFeatures::HeapUnifiedGarbageCollectionEnabled()) {
ThreadState::MainThreadState()->RegisterTraceDOMWrappers(
isolate, V8GCController::TraceDOMWrappers, nullptr, nullptr);
} else {
ThreadState::MainThreadState()->RegisterTraceDOMWrappers(
isolate, V8GCController::TraceDOMWrappers,
ScriptWrappableMarkingVisitor::InvalidateDeadObjectsInMarkingDeque,
ScriptWrappableMarkingVisitor::PerformCleanup);
}
InitializeV8Common(isolate);
isolate->SetOOMErrorHandler(ReportOOMErrorInMainThread);
if (RuntimeEnabledFeatures::BloatedRendererDetectionEnabled()) {
DCHECK(g_near_heap_limit_on_main_thread_callback_);
isolate->AddNearHeapLimitCallback(NearHeapLimitCallbackOnMainThread,
isolate);
}
isolate->SetFatalErrorHandler(ReportFatalErrorInMainThread);
isolate->AddMessageListenerWithErrorLevel(
MessageHandlerInMainThread,
v8::Isolate::kMessageError | v8::Isolate::kMessageWarning |
v8::Isolate::kMessageInfo | v8::Isolate::kMessageDebug |
v8::Isolate::kMessageLog);
isolate->SetFailedAccessCheckCallbackFunction(
FailedAccessCheckCallbackInMainThread);
isolate->SetAllowCodeGenerationFromStringsCallback(
CodeGenerationCheckCallbackInMainThread);
isolate->SetAllowWasmCodeGenerationCallback(
WasmCodeGenerationCheckCallbackInMainThread);
if (RuntimeEnabledFeatures::V8IdleTasksEnabled()) {
V8PerIsolateData::EnableIdleTasks(
isolate, std::make_unique<V8IdleTaskRunner>(scheduler));
}
isolate->SetPromiseRejectCallback(PromiseRejectHandlerInMainThread);
if (v8::HeapProfiler* profiler = isolate->GetHeapProfiler()) {
profiler->AddBuildEmbedderGraphCallback(
&V8EmbedderGraphBuilder::BuildEmbedderGraphCallback, nullptr);
}
V8PerIsolateData::From(isolate)->SetThreadDebugger(
std::make_unique<MainThreadDebugger>(isolate));
BindingSecurity::InitWrapperCreationSecurityCheck();
}
static void ReportFatalErrorInWorker(const char* location,
const char* message) {
// FIXME: We temporarily deal with V8 internal error situations such as
// out-of-memory by crashing the worker.
LOG(FATAL);
}
// Stack size for workers is limited to 500KB because default stack size for
// secondary threads is 512KB on Mac OS X. See GetDefaultThreadStackSize() in
// base/threading/platform_thread_mac.mm for details.
static const int kWorkerMaxStackSize = 500 * 1024;
void V8Initializer::InitializeWorker(v8::Isolate* isolate) {
InitializeV8Common(isolate);
isolate->AddMessageListenerWithErrorLevel(
MessageHandlerInWorker,
v8::Isolate::kMessageError | v8::Isolate::kMessageWarning |
v8::Isolate::kMessageInfo | v8::Isolate::kMessageDebug |
v8::Isolate::kMessageLog);
isolate->SetFatalErrorHandler(ReportFatalErrorInWorker);
isolate->SetStackLimit(WTF::GetCurrentStackPosition() - kWorkerMaxStackSize);
isolate->SetPromiseRejectCallback(PromiseRejectHandlerInWorker);
if (RuntimeEnabledFeatures::BloatedRendererDetectionEnabled()) {
isolate->AddNearHeapLimitCallback(NearHeapLimitCallbackOnWorkerThread,
isolate);
}
}
} // namespace blink