blob: 075adfdbaabb2ecb292646b6ad0c5868e207922f [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/loader/modulescript/module_script_loader.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/core/dom/dom_implementation.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_loader_client.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.h"
#include "third_party/blink/renderer/core/script/js_module_script.h"
#include "third_party/blink/renderer/core/script/modulator.h"
#include "third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h"
#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
namespace blink {
ModuleScriptLoader::ModuleScriptLoader(Modulator* modulator,
const ScriptFetchOptions& options,
ModuleScriptLoaderRegistry* registry,
ModuleScriptLoaderClient* client)
: modulator_(modulator),
options_(options),
registry_(registry),
client_(client) {
DCHECK(modulator);
DCHECK(registry);
DCHECK(client);
}
ModuleScriptLoader::~ModuleScriptLoader() = default;
#if DCHECK_IS_ON()
const char* ModuleScriptLoader::StateToString(ModuleScriptLoader::State state) {
switch (state) {
case State::kInitial:
return "Initial";
case State::kFetching:
return "Fetching";
case State::kFinished:
return "Finished";
}
NOTREACHED();
return "";
}
#endif
void ModuleScriptLoader::AdvanceState(ModuleScriptLoader::State new_state) {
switch (state_) {
case State::kInitial:
DCHECK_EQ(new_state, State::kFetching);
break;
case State::kFetching:
DCHECK_EQ(new_state, State::kFinished);
break;
case State::kFinished:
NOTREACHED();
break;
}
#if DCHECK_IS_ON()
RESOURCE_LOADING_DVLOG(1)
<< "ModuleLoader[" << url_.GetString() << "]::advanceState("
<< StateToString(state_) << " -> " << StateToString(new_state) << ")";
#endif
state_ = new_state;
if (state_ == State::kFinished) {
registry_->ReleaseFinishedLoader(this);
client_->NotifyNewSingleModuleFinished(module_script_);
}
}
void ModuleScriptLoader::Fetch(
const ModuleScriptFetchRequest& module_request,
ResourceFetcher* fetch_client_settings_object_fetcher,
ModuleGraphLevel level,
Modulator* module_map_settings_object,
ModuleScriptCustomFetchType custom_fetch_type,
ModuleScriptLoaderRegistry* registry,
ModuleScriptLoaderClient* client) {
ModuleScriptLoader* loader = MakeGarbageCollected<ModuleScriptLoader>(
module_map_settings_object, module_request.Options(), registry, client);
registry->AddLoader(loader);
loader->FetchInternal(module_request, fetch_client_settings_object_fetcher,
level, custom_fetch_type);
}
// <specdef href="https://html.spec.whatwg.org/C/#fetch-a-single-module-script">
void ModuleScriptLoader::FetchInternal(
const ModuleScriptFetchRequest& module_request,
ResourceFetcher* fetch_client_settings_object_fetcher,
ModuleGraphLevel level,
ModuleScriptCustomFetchType custom_fetch_type) {
const FetchClientSettingsObject& fetch_client_settings_object =
fetch_client_settings_object_fetcher->GetProperties()
.GetFetchClientSettingsObject();
// <spec step="4">Set moduleMap[url] to "fetching".</spec>
AdvanceState(State::kFetching);
// <spec step="5">Let request be a new request whose url is url, ...</spec>
ResourceRequest resource_request(module_request.Url());
#if DCHECK_IS_ON()
url_ = module_request.Url();
#endif
// <spec step="5">... destination is destination, ...</spec>
resource_request.SetRequestContext(module_request.ContextType());
resource_request.SetRequestDestination(module_request.Destination());
ResourceLoaderOptions options(&modulator_->GetScriptState()->World());
// <spec step="7">Set up the module script request given request and
// options.</spec>
//
// <specdef label="SMSR"
// href="https://html.spec.whatwg.org/C/#set-up-the-module-script-request">
// <spec label="SMSR">... its parser metadata to options's parser metadata,
// ...</spec>
options.parser_disposition = options_.ParserState();
// As initiator for module script fetch is not specified in HTML spec,
// we specify "" as initiator per:
// https://fetch.spec.whatwg.org/#concept-request-initiator
options.initiator_info.name = g_empty_atom;
// TODO(crbug.com/1064920): Remove this once PlzDedicatedWorker ships.
options.reject_coep_unsafe_none = options_.GetRejectCoepUnsafeNone();
if (level == ModuleGraphLevel::kDependentModuleFetch) {
options.initiator_info.is_imported_module = true;
options.initiator_info.referrer = module_request.ReferrerString();
options.initiator_info.position = module_request.GetReferrerPosition();
}
// Note: |options| should not be modified after here.
FetchParameters fetch_params(std::move(resource_request), options);
fetch_params.SetModuleScript();
// <spec label="SMSR">... its integrity metadata to options's integrity
// metadata, ...</spec>
fetch_params.SetIntegrityMetadata(options_.GetIntegrityMetadata());
fetch_params.MutableResourceRequest().SetFetchIntegrity(
options_.GetIntegrityAttributeValue());
// <spec label="SMSR">Set request's cryptographic nonce metadata to options's
// cryptographic nonce, ...</spec>
fetch_params.SetContentSecurityPolicyNonce(options_.Nonce());
// <spec label="SMSR">... its referrer policy to options's referrer
// policy.</spec>
fetch_params.MutableResourceRequest().SetReferrerPolicy(
module_request.Options().GetReferrerPolicy());
// <spec step="5">... mode is "cors", ...</spec>
//
// <spec label="SMSR">... its credentials mode to options's credentials mode,
// ...</spec>
fetch_params.SetCrossOriginAccessControl(
fetch_client_settings_object.GetSecurityOrigin(),
options_.CredentialsMode());
// <spec step="6">If destination is "worker" or "sharedworker" and the
// top-level module fetch flag is set, then set request's mode to
// "same-origin".</spec>
//
// `kServiceWorker` is included here for consistency, while it isn't mentioned
// in the spec. This doesn't affect the behavior, because we already forbid
// redirects and cross-origin response URLs in other places.
if ((module_request.Destination() ==
network::mojom::RequestDestination::kWorker ||
module_request.Destination() ==
network::mojom::RequestDestination::kSharedWorker ||
module_request.Destination() ==
network::mojom::RequestDestination::kServiceWorker) &&
level == ModuleGraphLevel::kTopLevelModuleFetch) {
// This should be done after SetCrossOriginAccessControl() that sets the
// mode to kCors.
fetch_params.MutableResourceRequest().SetMode(
network::mojom::RequestMode::kSameOrigin);
}
// <spec step="5">... referrer is referrer, ...</spec>
fetch_params.MutableResourceRequest().SetReferrerString(
module_request.ReferrerString());
// Priority Hints and a request's "importance" are currently non-standard, but
// we can assume the following (see https://crbug.com/821464):
// Step 5. "... importance is options's importance ..."
fetch_params.MutableResourceRequest().SetFetchImportanceMode(
options_.Importance());
// <spec step="5">... and client is fetch client settings object.</spec>
//
// -> set by ResourceFetcher
// Note: The fetch request's "origin" isn't specified in
// https://html.spec.whatwg.org/C/#fetch-a-single-module-script
// Thus, the "origin" is "client" per
// https://fetch.spec.whatwg.org/#concept-request-origin
// Module scripts are always defer.
fetch_params.SetDefer(FetchParameters::kLazyLoad);
// TODO(yoav): This is not accurate for module scripts with an async
// attribute.
fetch_params.SetRenderBlockingBehavior(RenderBlockingBehavior::kNonBlocking);
// [nospec] Unlike defer/async classic scripts, module scripts are fetched at
// High priority.
fetch_params.MutableResourceRequest().SetPriority(
ResourceLoadPriority::kHigh);
// <spec step="12.1">Let source text be the result of UTF-8 decoding
// response's body.</spec>
fetch_params.SetDecoderOptions(
TextResourceDecoderOptions::CreateUTF8Decode());
// <spec step="8">If the caller specified custom steps to perform the fetch,
// perform them on request, setting the is top-level flag if the top-level
// module fetch flag is set. Return from this algorithm, and when the custom
// perform the fetch steps complete with response response, run the remaining
// steps. Otherwise, fetch request. Return from this algorithm, and run the
// remaining steps as part of the fetch's process response for the response
// response.</spec>
module_fetcher_ =
modulator_->CreateModuleScriptFetcher(custom_fetch_type, PassKey());
module_fetcher_->Fetch(fetch_params, module_request.GetExpectedModuleType(),
fetch_client_settings_object_fetcher, level, this);
}
// <specdef href="https://html.spec.whatwg.org/C/#fetch-a-single-module-script">
void ModuleScriptLoader::NotifyFetchFinishedError(
const HeapVector<Member<ConsoleMessage>>& error_messages) {
// [nospec] Abort the steps if the browsing context is discarded.
if (!modulator_->HasValidContext()) {
AdvanceState(State::kFinished);
return;
}
// Note: "conditions" referred in Step 9 is implemented in
// WasModuleLoadSuccessful() in module_script_fetcher.cc.
// <spec step="9">If any of the following conditions are met, set
// moduleMap[url] to null, asynchronously complete this algorithm with null,
// and abort these steps: ...</spec>
for (ConsoleMessage* error_message : error_messages) {
ExecutionContext::From(modulator_->GetScriptState())
->AddConsoleMessage(error_message);
}
AdvanceState(State::kFinished);
}
void ModuleScriptLoader::NotifyFetchFinishedSuccess(
const ModuleScriptCreationParams& params) {
// [nospec] Abort the steps if the browsing context is discarded.
if (!modulator_->HasValidContext()) {
AdvanceState(State::kFinished);
return;
}
// <spec step="12.1">Let source text be the result of UTF-8 decoding
// response's body.</spec>
//
// <spec step="12.2">Set module script to the result of creating a JavaScript
// module script given source text, module map settings object, response's
// url, and options.</spec>
switch (params.GetModuleType()) {
case ModuleType::kJSON:
DCHECK(base::FeatureList::IsEnabled(blink::features::kJSONModules));
module_script_ = ValueWrapperSyntheticModuleScript::
CreateJSONWrapperSyntheticModuleScript(params, modulator_);
break;
case ModuleType::kCSS:
DCHECK(RuntimeEnabledFeatures::CSSModulesEnabled());
module_script_ = ValueWrapperSyntheticModuleScript::
CreateCSSWrapperSyntheticModuleScript(params, modulator_);
break;
case ModuleType::kJavaScript:
// Step 9. "Let source text be the result of UTF-8 decoding response's
// body." [spec text]
// Step 10. "Let module script be the result of creating
// a module script given source text, module map settings object,
// response's url, and options." [spec text]
module_script_ = JSModuleScript::Create(params, modulator_, options_);
break;
case ModuleType::kInvalid:
NOTREACHED();
}
AdvanceState(State::kFinished);
}
void ModuleScriptLoader::Trace(Visitor* visitor) const {
visitor->Trace(modulator_);
visitor->Trace(module_script_);
visitor->Trace(registry_);
visitor->Trace(client_);
visitor->Trace(module_fetcher_);
}
} // namespace blink