blob: d0eb45607473844843cdfa5ee7e8ab458fbbe2d7 [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/core/script/dynamic_module_resolver.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.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_global_scope.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
class DynamicImportTreeClient final : public ModuleTreeClient {
public:
DynamicImportTreeClient(const KURL& url,
Modulator* modulator,
ScriptPromiseResolver* promise_resolver)
: url_(url), modulator_(modulator), promise_resolver_(promise_resolver) {}
void Trace(Visitor*) override;
private:
// Implements ModuleTreeClient:
void NotifyModuleTreeLoadFinished(ModuleScript*) final;
const KURL url_;
const Member<Modulator> modulator_;
const Member<ScriptPromiseResolver> promise_resolver_;
};
// Implements steps 2.[5-8] of
// <specdef
// href="https://html.spec.whatwg.org/C/#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)">
void DynamicImportTreeClient::NotifyModuleTreeLoadFinished(
ModuleScript* module_script) {
// [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();
// <spec step="6">If result is null, then:</spec>
if (!module_script) {
// <spec step="6.1">Let completion be Completion { [[Type]]: throw,
// [[Value]]: a new TypeError, [[Target]]: empty }.</spec>
v8::Local<v8::Value> error = V8ThrowException::CreateTypeError(
isolate,
"Failed to fetch dynamically imported module: " + url_.GetString());
// <spec step="6.2">Perform FinishDynamicImport(referencingScriptOrModule,
// specifier, promiseCapability, completion).</spec>
promise_resolver_->Reject(error);
// <spec step="6.3">Return.</spec>
return;
}
// <spec step="7">Run the module script result, with the rethrow errors
// boolean set to true.</spec>
ScriptValue error = modulator_->ExecuteModule(
module_script, Modulator::CaptureEvalErrorFlag::kCapture);
// <spec step="8">If running the module script throws an exception, ...</spec>
if (!error.IsEmpty()) {
// <spec step="8">... then perform
// FinishDynamicImport(referencingScriptOrModule, specifier,
// promiseCapability, the thrown exception completion).</spec>
//
// Note: "the thrown exception completion" is |error|.
//
// <spec
// href="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>
promise_resolver_->Reject(error);
return;
}
// <spec step="9">Otherwise, perform
// FinishDynamicImport(referencingScriptOrModule, specifier,
// promiseCapability, NormalCompletion(undefined)).</spec>
//
// <spec
// href="https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport"
// step="2.1">Assert: completion is a normal completion and
// completion.[[Value]] is undefined.</spec>
DCHECK(error.IsEmpty());
// <spec
// href="https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport"
// step="2.2">Let moduleRecord be !
// HostResolveImportedModule(referencingScriptOrModule, specifier).</spec>
//
// Note: We skip invocation of ModuleRecordResolver here. The
// result of HostResolveImportedModule is guaranteed to be |module_script|.
v8::Local<v8::Module> record = module_script->V8Module();
DCHECK(!record.IsEmpty());
// <spec
// href="https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport"
// step="2.3">Assert: Evaluate has already been invoked on moduleRecord and
// successfully completed.</spec>
//
// Because |error| is empty, we are sure that ExecuteModule() above was
// successfully completed.
// <spec
// href="https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport"
// step="2.4">Let namespace be GetModuleNamespace(moduleRecord).</spec>
v8::Local<v8::Value> module_namespace = ModuleRecord::V8Namespace(record);
// <spec
// href="https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport"
// step="2.5">If namespace is an abrupt completion, perform !
// Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]]
// »).</spec>
//
// Note: Blink's implementation never allows |module_namespace| to be
// an abrupt completion.
// <spec
// href="https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport"
// step="2.6">Otherwise, perform ! Call(promiseCapability.[[Resolve]],
// undefined, « namespace.[[Value]] »).</spec>
promise_resolver_->Resolve(module_namespace);
}
void DynamicImportTreeClient::Trace(Visitor* visitor) {
visitor->Trace(modulator_);
visitor->Trace(promise_resolver_);
ModuleTreeClient::Trace(visitor);
}
} // namespace
void DynamicModuleResolver::Trace(Visitor* visitor) {
visitor->Trace(modulator_);
}
// <specdef
// href="https://html.spec.whatwg.org/C/#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)">
void DynamicModuleResolver::ResolveDynamically(
const String& specifier,
const KURL& referrer_resource_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://github.com/WICG/import-maps/blob/master/spec.md#when-import-maps-can-be-encountered
// Strictly, the flag should be cleared at
// #internal-module-script-graph-fetching-procedure, i.e. in ModuleTreeLinker,
// but due to https://crbug.com/928435 https://crbug.com/928564 we also clears
// the flag here, as import maps can be accessed earlier than specced below
// (in ResolveModuleSpecifier()) and we need to clear the flag before that.
modulator_->ClearIsAcquiringImportMaps();
// <spec step="4.1">Let referencing script be
// referencingScriptOrModule.[[HostDefined]].</spec>
// <spec step="4.3">Set base URL to referencing script's base URL.</spec>
KURL base_url = referrer_info.BaseURL();
if (base_url.IsNull()) {
// ReferrerScriptInfo::BaseURL returns null if it should defer to referrer
// resource url.
base_url = referrer_resource_url;
}
if (base_url.IsNull()) {
// The case where "referencing script" doesn't exist.
//
// <spec step="1">Let settings object be the current settings object.</spec>
//
// <spec step="2">Let base URL be settings object's API base URL.</spec>
base_url = ExecutionContext::From(modulator_->GetScriptState())->BaseURL();
}
DCHECK(!base_url.IsNull());
// <spec step="5">Fetch an import() module script graph given specifier, base
// URL, settings object, and fetch options. Wait until the algorithm
// asynchronously completes with result.</spec>
//
// <specdef label="fetch-an-import()-module-script-graph"
// href="https://html.spec.whatwg.org/C/#fetch-an-import()-module-script-graph">
// <spec label="fetch-an-import()-module-script-graph" step="1">Let url be the
// result of resolving a module specifier given base URL and specifier.</spec>
KURL url = modulator_->ResolveModuleSpecifier(specifier, base_url);
// <spec label="fetch-an-import()-module-script-graph" step="2">If url is
// failure, then asynchronously complete this algorithm with null, and abort
// these steps.</spec>
if (!url.IsValid()) {
// <spec step="6">If result is null, then:</spec>
//
// <spec step="6.1">Let completion be Completion { [[Type]]: throw,
// [[Value]]: a new TypeError, [[Target]]: empty }.</spec>
v8::Isolate* isolate = modulator_->GetScriptState()->GetIsolate();
v8::Local<v8::Value> error = V8ThrowException::CreateTypeError(
isolate, "Failed to resolve module specifier '" + specifier + "'");
// <spec step="6.2">Perform FinishDynamicImport(referencingScriptOrModule,
// specifier, promiseCapability, completion).</spec>
//
// <spec
// href="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>
promise_resolver->Reject(error);
// <spec step="6.3">Return.</spec>
return;
}
// <spec step="4.4">Set fetch options to the descendant script fetch options
// for referencing script's fetch options.</spec>
//
// <spec
// href="https://html.spec.whatwg.org/C/#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>
//
// TODO(domfarolino): It has not yet been decided how a script's "importance"
// should affect its dynamic imports. There is discussion at
// https://github.com/whatwg/html/issues/3670, but for now there is no effect,
// and dynamic imports get kImportanceAuto. If this changes,
// ReferrerScriptInfo will need a mojom::FetchImportanceMode member, that must
// be properly set.
ScriptFetchOptions options(referrer_info.Nonce(), IntegrityMetadataSet(),
String(), referrer_info.ParserState(),
referrer_info.CredentialsMode(),
referrer_info.GetReferrerPolicy(),
mojom::FetchImportanceMode::kImportanceAuto);
// <spec label="fetch-an-import()-module-script-graph" step="3">Fetch a single
// module script given url, settings object, "script", options, settings
// object, "client", and with the top-level module fetch flag set. If the
// caller of this algorithm specified custom perform the fetch steps, pass
// those along as well. Wait until the algorithm asynchronously completes with
// result.</spec>
auto* tree_client = MakeGarbageCollected<DynamicImportTreeClient>(
url, modulator_.Get(), promise_resolver);
// TODO(kouhei): ExecutionContext::From(modulator_->GetScriptState()) is
// highly discouraged since it breaks layering. Rewrite this.
auto* execution_context =
ExecutionContext::From(modulator_->GetScriptState());
if (auto* scope = DynamicTo<WorkerGlobalScope>(*execution_context))
scope->EnsureFetcher();
modulator_->FetchTree(url, execution_context->Fetcher(),
mojom::RequestContextType::SCRIPT,
network::mojom::RequestDestination::kScript, options,
ModuleScriptCustomFetchType::kNone, tree_client);
// Steps 6-9 are implemented at
// DynamicImportTreeClient::NotifyModuleLoadFinished.
// <spec step="10">Return undefined.</spec>
}
} // namespace blink