blob: 9d7a2e02bf1eed495877e5247e2bbb1d9af0c42b [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 "core/dom/DynamicModuleResolver.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ReferrerScriptInfo.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/dom/Modulator.h"
#include "core/dom/ModuleScript.h"
#include "core/loader/modulescript/ModuleScriptFetchRequest.h"
#include "platform/bindings/V8ThrowException.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
class DynamicImportTreeClient final : public ModuleTreeClient {
public:
static DynamicImportTreeClient* Create(
const KURL& url,
Modulator* modulator,
ScriptPromiseResolver* promise_resolver) {
return new DynamicImportTreeClient(url, modulator, promise_resolver);
}
void Trace(blink::Visitor*);
private:
DynamicImportTreeClient(const KURL& url,
Modulator* modulator,
ScriptPromiseResolver* promise_resolver)
: url_(url), modulator_(modulator), promise_resolver_(promise_resolver) {}
// Implements ModuleTreeClient:
void NotifyModuleTreeLoadFinished(ModuleScript*) final;
const KURL url_;
const Member<Modulator> modulator_;
const Member<ScriptPromiseResolver> promise_resolver_;
};
void DynamicImportTreeClient::NotifyModuleTreeLoadFinished(
ModuleScript* module_script) {
// Implements steps 2.[5-8] of
// https://html.spec.whatwg.org/multipage/webappapis.html#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)
// [nospec] Abort the steps if the browsing context is discarded.
if (!modulator_->HasValidContext()) {
// The promise_resolver_ should have ::Detach()-ed at this point,
// so ::Reject() is not necessary.
return;
}
ScriptState* script_state = modulator_->GetScriptState();
ScriptState::Scope scope(script_state);
v8::Isolate* isolate = script_state->GetIsolate();
// Step 2.5. "If result is null, then:" [spec text]
if (!module_script) {
// Step 2.5.1. "Let completion be Completion { [[Type]]: throw, [[Value]]: a
// new TypeError, [[Target]]: empty }." [spec text]
v8::Local<v8::Value> error = V8ThrowException::CreateTypeError(
isolate,
"Failed to fetch dynamically imported module: " + url_.GetString());
// Step 2.5.2. "Perform FinishDynamicImport(referencingScriptOrModule,
// specifier, promiseCapability, completion)." [spec text]
promise_resolver_->Reject(error);
// Step 2.5.3. "Abort these steps."
return;
}
// Step 2.6. "Run the module script module script, with the rethrow errors
// boolean set to true." [spec text]
ScriptValue error =
modulator_->ExecuteModule(module_script, CaptureEvalErrorFlag::kCapture);
// Step 2.7. "If running the module script throws an exception, ..." [spec
// text]
if (!error.IsEmpty()) {
// "... then perform FinishDynamicImport(referencingScriptOrModule,
// specifier, promiseCapability, the thrown exception completion)."
// [spec text]
// Note: "the thrown exception completion" is |error|.
// https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport
// Step 1. "If completion is an abrupt completion, then perform !
// Call(promiseCapability.[[Reject]], undefined, << completion.[[Value]]
// >>)." [spec text]
promise_resolver_->Reject(error);
return;
}
// Step 2.8. "Otherwise, perform
// FinishDynamicImport(referencingScriptOrModule, specifier,
// promiseCapability, NormalCompletion(undefined))." [spec text]
// https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport
// Step 2.a. "Assert: completion is a normal completion and
// completion.[[Value]] is undefined." [spec text]
DCHECK(error.IsEmpty());
// Step 2.b. "Let moduleRecord be
// !HostResolveImportedModule(referencingScriptOrModule, specifierString)."
// [spec text]
// Note: We skip invocation of ScriptModuleResolver here. The
// result of HostResolveImportedModule is guaranteed to be |module_script|.
ScriptModule record = module_script->Record();
DCHECK(!record.IsNull());
// Step 2.c. "Assert: ModuleEvaluation has already been invoked on
// moduleRecord and successfully completed." [spec text]
DCHECK_EQ(ScriptModuleState::kEvaluated, modulator_->GetRecordStatus(record));
// Step 2.d. "Let namespace be GetModuleNamespace(moduleRecord)." [spec text]
v8::Local<v8::Value> module_namespace = record.V8Namespace(isolate);
// Step 2.e. "If namespace is an abrupt completion, perform
// !Call(promiseCapability.[[Reject]], undefined, << namespace.[[Value]] >>)."
// [spec text]
// Note: Blink's implementation never allows |module_namespace| to be
// an abrupt completion.
// Step 2.f "Otherwise, perform ! Call(promiseCapability.[[Resolve]],
// undefined, << namespace.[[Value]] >>)." [spec text]
promise_resolver_->Resolve(module_namespace);
}
void DynamicImportTreeClient::Trace(blink::Visitor* visitor) {
visitor->Trace(modulator_);
visitor->Trace(promise_resolver_);
ModuleTreeClient::Trace(visitor);
}
} // namespace
void DynamicModuleResolver::Trace(blink::Visitor* visitor) {
visitor->Trace(modulator_);
}
void DynamicModuleResolver::ResolveDynamically(
const String& specifier,
const KURL& referrer_url,
const ReferrerScriptInfo& referrer_info,
ScriptPromiseResolver* promise_resolver) {
DCHECK(modulator_->GetScriptState()->GetIsolate()->InContext())
<< "ResolveDynamically should be called from V8 callback, within a valid "
"context.";
// https://html.spec.whatwg.org/multipage/webappapis.html#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)
// Step 1. "Let referencing script be
// referencingScriptOrModule.[[HostDefined]]." [spec text]
// Step 2. "Run the following steps in parallel:"
// Step 2.1. "Let url be the result of resolving a module specifier
// given referencing script and specifier." [spec text]
KURL context_url =
referrer_url.IsValid()
? referrer_url
: ExecutionContext::From(modulator_->GetScriptState())->Url();
DCHECK(context_url.IsValid());
KURL url = Modulator::ResolveModuleSpecifier(specifier, context_url);
if (!url.IsValid()) {
// Step 2.2.1. "If the result is failure, then:" [spec text]
// Step 2.2.2.1. "Let completion be Completion { [[Type]]: throw, [[Value]]:
// a new TypeError, [[Target]]: empty }." [spec text]
v8::Isolate* isolate = modulator_->GetScriptState()->GetIsolate();
v8::Local<v8::Value> error = V8ThrowException::CreateTypeError(
isolate, "Failed to resolve module specifier '" + specifier + "'");
// Step 2.2.2.2. "Perform FinishDynamicImport(referencingScriptOrModule,
// specifier, promiseCapability, completion)" [spec text]
// https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport
// Step 1. "If completion is an abrupt completion, then perform
// !Call(promiseCapability.[[Reject]], undefined, <<completion.[[Value]]>>).
// " [spec text]
promise_resolver->Reject(error);
// Step 2.2.2.3. "Abort these steps." [spec text]
return;
}
// Step 2.3. "Let options be the descendant script fetch options for
// referencing script's fetch options." [spec text]
// https://html.spec.whatwg.org/multipage/webappapis.html#descendant-script-fetch-options
// "For any given script fetch options options, the descendant script fetch
// options are a new script fetch options whose items all have the same
// values, except for the integrity metadata, which is instead the empty
// string." [spec text]
ScriptFetchOptions options(referrer_info.Nonce(), IntegrityMetadataSet(),
String(), referrer_info.ParserState(),
referrer_info.CredentialsMode());
ModuleScriptFetchRequest request(url, modulator_->GetReferrerPolicy(),
options);
// Step 2.4. "Fetch a module script graph given url, settings object,
// "script", and options. Wait until the algorithm asynchronously completes
// with result."
auto tree_client =
DynamicImportTreeClient::Create(url, modulator_.Get(), promise_resolver);
modulator_->FetchTree(request, tree_client);
// Steps 2.[5-8] are implemented at
// DynamicImportTreeClient::NotifyModuleLoadFinished.
// Step 3. "Return undefined." [spec text]
}
} // namespace blink