| // 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/core/script/module_script.h" |
| |
| #include "third_party/blink/renderer/bindings/core/v8/script_value.h" |
| #include "third_party/blink/renderer/core/script/script_module_resolver.h" |
| #include "third_party/blink/renderer/platform/bindings/script_state.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| // https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-module-script |
| ModuleScript* ModuleScript::Create(const ParkableString& original_source_text, |
| Modulator* modulator, |
| const KURL& source_url, |
| const KURL& base_url, |
| const ScriptFetchOptions& options, |
| AccessControlStatus access_control_status, |
| const TextPosition& start_position) { |
| // <spec step="1">If scripting is disabled for settings's responsible browsing |
| // context, then set source to the empty string.</spec> |
| ParkableString source_text; |
| if (!modulator->IsScriptingDisabled()) |
| source_text = original_source_text; |
| |
| // <spec step="2">Let script be a new module script that this algorithm will |
| // subsequently initialize.</spec> |
| |
| // <spec step="3">Set script's settings object to settings.</spec> |
| // |
| // Note: "script's settings object" will be |modulator|. |
| |
| // <spec step="7">Let result be ParseModule(source, settings's Realm, |
| // script).</spec> |
| ScriptState* script_state = modulator->GetScriptState(); |
| ScriptState::Scope scope(script_state); |
| v8::Isolate* isolate = script_state->GetIsolate(); |
| ExceptionState exception_state(isolate, ExceptionState::kExecutionContext, |
| "ModuleScript", "Create"); |
| |
| ScriptModule result = ScriptModule::Compile( |
| isolate, source_text.ToString(), source_url, base_url, options, |
| access_control_status, start_position, exception_state); |
| |
| // CreateInternal processes Steps 4 and 8-10. |
| // |
| // [nospec] We initialize the other ModuleScript members anyway by running |
| // Steps 8-13 before Step 6. In a case that compile failed, we will |
| // immediately turn the script into errored state. Thus the members will not |
| // be used for the speced algorithms, but may be used from inspector. |
| ModuleScript* script = |
| CreateInternal(source_text, modulator, result, source_url, base_url, |
| options, start_position); |
| |
| // <spec step="8">If result is a list of errors, then:</spec> |
| if (exception_state.HadException()) { |
| DCHECK(result.IsNull()); |
| |
| // <spec step="8.1">Set script's parse error to result[0].</spec> |
| v8::Local<v8::Value> error = exception_state.GetException(); |
| exception_state.ClearException(); |
| script->SetParseErrorAndClearRecord(ScriptValue(script_state, error)); |
| |
| // <spec step="8.2">Return script.</spec> |
| return script; |
| } |
| |
| // <spec step="9">For each string requested of |
| // result.[[RequestedModules]]:</spec> |
| for (const auto& requested : |
| modulator->ModuleRequestsFromScriptModule(result)) { |
| // <spec step="9.1">Let url be the result of resolving a module specifier |
| // given script and requested.</spec> |
| // |
| // <spec step="9.2">If url is failure, then:</spec> |
| String failure_reason; |
| if (script->ResolveModuleSpecifier(requested.specifier, &failure_reason) |
| .IsValid()) |
| continue; |
| |
| // <spec step="9.2.1">Let error be a new TypeError exception.</spec> |
| String error_message = "Failed to resolve module specifier \"" + |
| requested.specifier + "\". " + failure_reason; |
| v8::Local<v8::Value> error = |
| V8ThrowException::CreateTypeError(isolate, error_message); |
| |
| // <spec step="9.2.2">Set script's parse error to error.</spec> |
| script->SetParseErrorAndClearRecord(ScriptValue(script_state, error)); |
| |
| // <spec step="9.2.3">Return script.</spec> |
| return script; |
| } |
| |
| // <spec step="11">Return script.</spec> |
| return script; |
| } |
| |
| ModuleScript* ModuleScript::CreateForTest(Modulator* modulator, |
| ScriptModule record, |
| const KURL& base_url, |
| const ScriptFetchOptions& options) { |
| ParkableString dummy_source_text(String("").ReleaseImpl()); |
| KURL dummy_source_url; |
| return CreateInternal(dummy_source_text, modulator, record, dummy_source_url, |
| base_url, options, TextPosition::MinimumPosition()); |
| } |
| |
| // https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-module-script |
| ModuleScript* ModuleScript::CreateInternal(const ParkableString& source_text, |
| Modulator* modulator, |
| ScriptModule result, |
| const KURL& source_url, |
| const KURL& base_url, |
| const ScriptFetchOptions& options, |
| const TextPosition& start_position) { |
| // <spec step="6">Set script's parse error and error to rethrow to |
| // null.</spec> |
| // |
| // <spec step="10">Set script's record to result.</spec> |
| // |
| // <spec step="4">Set script's base URL to baseURL.</spec> |
| // |
| // <spec step="5">Set script's fetch options to options.</spec> |
| // |
| // [nospec] |source_text| is saved for CSP checks. |
| ModuleScript* module_script = |
| new ModuleScript(modulator, result, source_url, base_url, options, |
| source_text, start_position); |
| |
| // Step 7, a part of ParseModule(): Passing script as the last parameter |
| // here ensures result.[[HostDefined]] will be script. |
| modulator->GetScriptModuleResolver()->RegisterModuleScript(module_script); |
| |
| return module_script; |
| } |
| |
| ModuleScript::ModuleScript(Modulator* settings_object, |
| ScriptModule record, |
| const KURL& source_url, |
| const KURL& base_url, |
| const ScriptFetchOptions& fetch_options, |
| const ParkableString& source_text, |
| const TextPosition& start_position) |
| : Script(fetch_options, base_url), |
| settings_object_(settings_object), |
| source_text_(source_text), |
| start_position_(start_position), |
| source_url_(source_url) { |
| if (record.IsNull()) { |
| // We allow empty records for module infra tests which never touch records. |
| // This should never happen outside unit tests. |
| return; |
| } |
| |
| DCHECK(settings_object); |
| v8::Isolate* isolate = settings_object_->GetScriptState()->GetIsolate(); |
| v8::HandleScope scope(isolate); |
| record_.Set(isolate, record.NewLocal(isolate)); |
| } |
| |
| ScriptModule ModuleScript::Record() const { |
| if (record_.IsEmpty()) |
| return ScriptModule(); |
| |
| v8::Isolate* isolate = settings_object_->GetScriptState()->GetIsolate(); |
| v8::HandleScope scope(isolate); |
| return ScriptModule(isolate, record_.NewLocal(isolate), source_url_); |
| } |
| |
| bool ModuleScript::HasEmptyRecord() const { |
| return record_.IsEmpty(); |
| } |
| |
| void ModuleScript::SetParseErrorAndClearRecord(ScriptValue error) { |
| DCHECK(!error.IsEmpty()); |
| |
| record_.Clear(); |
| ScriptState::Scope scope(error.GetScriptState()); |
| parse_error_.Set(error.GetIsolate(), error.V8Value()); |
| } |
| |
| ScriptValue ModuleScript::CreateParseError() const { |
| ScriptState* script_state = settings_object_->GetScriptState(); |
| v8::Isolate* isolate = script_state->GetIsolate(); |
| ScriptState::Scope scope(script_state); |
| ScriptValue error(script_state, parse_error_.NewLocal(isolate)); |
| DCHECK(!error.IsEmpty()); |
| return error; |
| } |
| |
| void ModuleScript::SetErrorToRethrow(ScriptValue error) { |
| ScriptState::Scope scope(error.GetScriptState()); |
| error_to_rethrow_.Set(error.GetIsolate(), error.V8Value()); |
| } |
| |
| ScriptValue ModuleScript::CreateErrorToRethrow() const { |
| ScriptState* script_state = settings_object_->GetScriptState(); |
| v8::Isolate* isolate = script_state->GetIsolate(); |
| ScriptState::Scope scope(script_state); |
| ScriptValue error(script_state, error_to_rethrow_.NewLocal(isolate)); |
| DCHECK(!error.IsEmpty()); |
| return error; |
| } |
| |
| KURL ModuleScript::ResolveModuleSpecifier(const String& module_request, |
| String* failure_reason) { |
| auto found = specifier_to_url_cache_.find(module_request); |
| if (found != specifier_to_url_cache_.end()) |
| return found->value; |
| |
| KURL url = settings_object_->ResolveModuleSpecifier(module_request, BaseURL(), |
| failure_reason); |
| // Cache the result only on success, so that failure_reason is set for |
| // subsequent calls too. |
| if (url.IsValid()) |
| specifier_to_url_cache_.insert(module_request, url); |
| return url; |
| } |
| |
| void ModuleScript::Trace(blink::Visitor* visitor) { |
| visitor->Trace(settings_object_); |
| visitor->Trace(record_.UnsafeCast<v8::Value>()); |
| visitor->Trace(parse_error_); |
| visitor->Trace(error_to_rethrow_); |
| Script::Trace(visitor); |
| } |
| |
| void ModuleScript::RunScript(LocalFrame* frame, const SecurityOrigin*) const { |
| DVLOG(1) << *this << "::RunScript()"; |
| settings_object_->ExecuteModule(this, |
| Modulator::CaptureEvalErrorFlag::kReport); |
| } |
| |
| String ModuleScript::InlineSourceTextForCSP() const { |
| return source_text_.ToString(); |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const ModuleScript& module_script) { |
| stream << "ModuleScript[" << &module_script; |
| if (module_script.HasEmptyRecord()) |
| stream << ", empty-record"; |
| |
| if (module_script.HasErrorToRethrow()) |
| stream << ", error-to-rethrow"; |
| |
| if (module_script.HasParseError()) |
| stream << ", parse-error"; |
| |
| return stream << "]"; |
| } |
| |
| } // namespace blink |