blob: 6dd4e486d90d7b16ff05ee67d3b951effa683abd [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/bindings/core/v8/module_record.h"
#include "base/feature_list.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/boxed_v8_module.h"
#include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.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/modulator.h"
#include "third_party/blink/renderer/core/script/module_record_resolver.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/text_position.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
namespace blink {
ModuleRecordProduceCacheData::ModuleRecordProduceCacheData(
v8::Isolate* isolate,
SingleCachedMetadataHandler* cache_handler,
V8CodeCache::ProduceCacheOptions produce_cache_options,
v8::Local<v8::Module> module)
: cache_handler_(cache_handler),
produce_cache_options_(produce_cache_options) {
v8::HandleScope scope(isolate);
if (produce_cache_options ==
V8CodeCache::ProduceCacheOptions::kProduceCodeCache &&
module->GetStatus() == v8::Module::kUninstantiated) {
v8::Local<v8::UnboundModuleScript> unbound_script =
module->GetUnboundModuleScript();
if (!unbound_script.IsEmpty())
unbound_script_.Set(isolate, unbound_script);
}
}
void ModuleRecordProduceCacheData::Trace(Visitor* visitor) const {
visitor->Trace(cache_handler_);
visitor->Trace(unbound_script_.UnsafeCast<v8::Value>());
}
v8::Local<v8::Module> ModuleRecord::Compile(
v8::Isolate* isolate,
const ModuleScriptCreationParams& params,
const ScriptFetchOptions& options,
const TextPosition& text_position,
ExceptionState& exception_state,
mojom::blink::V8CacheOptions v8_cache_options,
ModuleRecordProduceCacheData** out_produce_cache_data) {
v8::TryCatch try_catch(isolate);
v8::Local<v8::Module> module;
// Module scripts currently don't support |kEagerCompile| which can be
// used for |mojom::blink::V8CacheOptions::kFullCodeWithoutHeatCheck|, so use
// |mojom::blink::V8CacheOptions::kCodeWithoutHeatCheck| instead.
if (v8_cache_options ==
mojom::blink::V8CacheOptions::kFullCodeWithoutHeatCheck) {
v8_cache_options = mojom::blink::V8CacheOptions::kCodeWithoutHeatCheck;
}
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(v8_cache_options, params.CacheHandler(),
params.GetSourceText().length(),
params.SourceLocationType());
if (!V8ScriptRunner::CompileModule(
isolate, params, text_position, compile_options, no_cache_reason,
ReferrerScriptInfo(params.BaseURL(), options,
ReferrerScriptInfo::BaseUrlSource::kOther))
.ToLocal(&module)) {
DCHECK(try_catch.HasCaught());
exception_state.RethrowV8Exception(try_catch.Exception());
return v8::Local<v8::Module>();
}
DCHECK(!try_catch.HasCaught());
if (out_produce_cache_data) {
*out_produce_cache_data =
MakeGarbageCollected<ModuleRecordProduceCacheData>(
isolate, params.CacheHandler(), produce_cache_options, module);
}
return module;
}
ScriptValue ModuleRecord::Instantiate(ScriptState* script_state,
v8::Local<v8::Module> record,
const KURL& source_url) {
v8::Isolate* isolate = script_state->GetIsolate();
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
DCHECK(!record.IsEmpty());
v8::Local<v8::Context> context = script_state->GetContext();
// Script IDs are not available on errored modules or on non-source text
// modules, so we give them a default value.
probe::ExecuteScript probe(ExecutionContext::From(script_state), source_url,
record->GetStatus() != v8::Module::kErrored &&
record->IsSourceTextModule()
? record->ScriptId()
: v8::UnboundScript::kNoScriptId);
bool success;
if (!record->InstantiateModule(context, &ResolveModuleCallback)
.To(&success) ||
!success) {
DCHECK(try_catch.HasCaught());
return ScriptValue(isolate, try_catch.Exception());
}
DCHECK(!try_catch.HasCaught());
return ScriptValue();
}
void ModuleRecord::ReportException(ScriptState* script_state,
v8::Local<v8::Value> exception) {
V8ScriptRunner::ReportException(script_state->GetIsolate(), exception);
}
Vector<ModuleRequest> ModuleRecord::ModuleRequests(
ScriptState* script_state,
v8::Local<v8::Module> record) {
if (record.IsEmpty())
return Vector<ModuleRequest>();
v8::Local<v8::FixedArray> v8_module_requests = record->GetModuleRequests();
int length = v8_module_requests->Length();
Vector<ModuleRequest> requests;
requests.ReserveInitialCapacity(length);
bool needs_text_position =
!WTF::IsMainThread() ||
probe::ToCoreProbeSink(ExecutionContext::From(script_state))
->HasDevToolsSessions();
for (int i = 0; i < length; ++i) {
v8::Local<v8::ModuleRequest> v8_module_request =
v8_module_requests->Get(script_state->GetContext(), i)
.As<v8::ModuleRequest>();
v8::Local<v8::String> v8_specifier = v8_module_request->GetSpecifier();
TextPosition position = TextPosition::MinimumPosition();
if (needs_text_position) {
// The source position is only used by DevTools for module requests and
// only visible if devtools is open when the request is initiated.
// Calculating the source position is not free and V8 has to initialize
// the line end information for the complete module, thus we try to
// avoid this additional work here if DevTools is closed.
int source_offset = v8_module_request->GetSourceOffset();
v8::Location v8_loc = record->SourceOffsetToLocation(source_offset);
position = TextPosition(
OrdinalNumber::FromZeroBasedInt(v8_loc.GetLineNumber()),
OrdinalNumber::FromZeroBasedInt(v8_loc.GetColumnNumber()));
}
Vector<ImportAssertion> import_assertions =
ModuleRecord::ToBlinkImportAssertions(
script_state->GetContext(), record,
v8_module_request->GetImportAssertions(),
/*v8_import_assertions_has_positions=*/true);
requests.emplace_back(ToCoreString(v8_specifier), position,
import_assertions);
}
return requests;
}
v8::Local<v8::Value> ModuleRecord::V8Namespace(v8::Local<v8::Module> record) {
DCHECK(!record.IsEmpty());
return record->GetModuleNamespace();
}
v8::MaybeLocal<v8::Module> ModuleRecord::ResolveModuleCallback(
v8::Local<v8::Context> context,
v8::Local<v8::String> specifier,
v8::Local<v8::FixedArray> import_assertions,
v8::Local<v8::Module> referrer) {
v8::Isolate* isolate = context->GetIsolate();
Modulator* modulator = Modulator::From(ScriptState::From(context));
DCHECK(modulator);
ModuleRequest module_request(
ToCoreStringWithNullCheck(specifier), TextPosition(),
ModuleRecord::ToBlinkImportAssertions(
context, referrer, import_assertions,
/*v8_import_assertions_has_positions=*/true));
ExceptionState exception_state(isolate, ExceptionState::kExecutionContext,
"ModuleRecord", "resolveModuleCallback");
v8::Local<v8::Module> resolved =
modulator->GetModuleRecordResolver()->Resolve(module_request, referrer,
exception_state);
DCHECK(!resolved.IsEmpty());
DCHECK(!exception_state.HadException());
return resolved;
}
Vector<ImportAssertion> ModuleRecord::ToBlinkImportAssertions(
v8::Local<v8::Context> context,
v8::Local<v8::Module> record,
v8::Local<v8::FixedArray> v8_import_assertions,
bool v8_import_assertions_has_positions) {
// If v8_import_assertions_has_positions == true then v8_import_assertions has
// source position information and is given in the form [key1, value1,
// source_offset1, key2, value2, source_offset2, ...]. Otherwise if
// v8_import_assertions_has_positions == false, then v8_import_assertions is
// in the form [key1, value1, key2, value2, ...].
const int kV8AssertionEntrySize = v8_import_assertions_has_positions ? 3 : 2;
Vector<ImportAssertion> import_assertions;
int number_of_import_assertions =
v8_import_assertions->Length() / kV8AssertionEntrySize;
import_assertions.ReserveInitialCapacity(number_of_import_assertions);
for (int i = 0; i < number_of_import_assertions; ++i) {
v8::Local<v8::String> v8_assertion_key =
v8_import_assertions->Get(context, i * kV8AssertionEntrySize)
.As<v8::String>();
v8::Local<v8::String> v8_assertion_value =
v8_import_assertions->Get(context, (i * kV8AssertionEntrySize) + 1)
.As<v8::String>();
TextPosition assertion_position;
if (v8_import_assertions_has_positions) {
int32_t v8_assertion_source_offset =
v8_import_assertions->Get(context, (i * kV8AssertionEntrySize) + 2)
.As<v8::Int32>()
->Value();
v8::Location v8_assertion_loc =
record->SourceOffsetToLocation(v8_assertion_source_offset);
assertion_position = TextPosition(
OrdinalNumber::FromZeroBasedInt(v8_assertion_loc.GetLineNumber()),
OrdinalNumber::FromZeroBasedInt(v8_assertion_loc.GetColumnNumber()));
}
import_assertions.emplace_back(ToCoreString(v8_assertion_key),
ToCoreString(v8_assertion_value),
assertion_position);
}
return import_assertions;
}
} // namespace blink