Commit 80b8282d authored by Kunihiko Sakamoto's avatar Kunihiko Sakamoto Committed by Commit Bot

Implement link rel=modulepreload

This implements link rel=modulepreload without submodules fetch,
behind the Experimental Web Platform feature flag.

LinkLoader fetches module script using Modulator::FetchSingle, so it
populates the module map but does not fetch descendant modules.
Unlike the preload fetches by link rel=preload, this doesn't set
FetchParameters::SetLinkPreload flag, so it works like a regular
script fetch.

HTMLPreloadScanner fetches module scripts in a similar way to
speculative preload of <script type=module>, i.e. just fetch a
ScriptResource and not populate the module map.

Spec PR: https://github.com/whatwg/html/pull/2383
Intent to implement:
https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/ynkrM70KDD4

Bug: 740886
Change-Id: I5f4420e305f4962b3fbe3f703ce95a5a43e4f7a9
Reviewed-on: https://chromium-review.googlesource.com/662697
Commit-Queue: Kunihiko Sakamoto <ksakamoto@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarHiroki Nakagawa <nhiroki@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#515490}
parent 4536c5cc
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
function verifyNumberOfDownloads(url, number) {
var numDownloads = 0;
let absoluteURL = new URL(url, location.href).href;
performance.getEntriesByName(absoluteURL).forEach(entry => {
if (entry.transferSize > 0) {
numDownloads++;
}
});
assert_equals(numDownloads, number, url);
}
function attachAndWaitForLoad(element) {
return new Promise((resolve, reject) => {
element.onload = resolve;
element.onerror = reject;
document.body.appendChild(element);
});
}
function attachAndWaitForError(element) {
return new Promise((resolve, reject) => {
element.onload = reject;
element.onerror = resolve;
document.body.appendChild(element);
});
}
promise_test(function(t) {
var link = document.createElement('link');
link.rel = 'modulepreload';
link.href = 'resources/dummy.js';
return attachAndWaitForLoad(link).then(() => {
verifyNumberOfDownloads('resources/dummy.js', 1);
// Verify that <script> doesn't fetch the module again.
var script = document.createElement('script');
script.type = 'module';
script.src = 'resources/dummy.js';
return attachAndWaitForLoad(script);
}).then(() => {
verifyNumberOfDownloads('resources/dummy.js', 1);
});
}, 'link rel=modulepreload');
promise_test(function(t) {
var link = document.createElement('link');
link.rel = 'modulepreload';
link.href = 'resources/module1.js';
return attachAndWaitForLoad(link).then(() => {
// Currently, modulepreload doesn't perform submodules fetch.
verifyNumberOfDownloads('resources/module1.js', 1);
verifyNumberOfDownloads('resources/module2.js', 0);
var script = document.createElement('script');
script.type = 'module';
script.src = 'resources/module1.js';
return attachAndWaitForLoad(script);
}).then(() => {
verifyNumberOfDownloads('resources/module1.js', 1);
verifyNumberOfDownloads('resources/module2.js', 1);
});
}, 'link rel=modulepreload with submodules');
promise_test(function(t) {
var link = document.createElement('link');
link.rel = 'modulepreload';
link.href = 'resources/syntax-error.js';
return attachAndWaitForError(link);
}, 'link rel=modulepreload for a module with syntax error');
promise_test(function(t) {
var link = document.createElement('link');
link.rel = 'modulepreload';
link.href = 'resources/not-exist.js';
return attachAndWaitForError(link);
}, 'link rel=modulepreload for a module with network error');
promise_test(function(t) {
var link = document.createElement('link');
link.rel = 'modulepreload';
link.href = null;
return attachAndWaitForError(link);
}, 'link rel=modulepreload with bad href attribute');
</script>
</body>
import { y } from './module2.js';
export let x = y + 1;
......@@ -194,7 +194,8 @@ void DynamicModuleResolver::ResolveDynamically(
ScriptFetchOptions options(referrer_info.Nonce(), IntegrityMetadataSet(),
String(), referrer_info.ParserState(),
referrer_info.CredentialsMode());
ModuleScriptFetchRequest request(url, options);
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
......
......@@ -47,6 +47,7 @@ class DynamicModuleResolverTestModulator final : public DummyModulator {
private:
// Implements Modulator:
ReferrerPolicy GetReferrerPolicy() override { return kReferrerPolicyDefault; }
ScriptState* GetScriptState() final { return script_state_.get(); }
ModuleScript* GetFetchedModuleScript(const KURL& url) final {
......
......@@ -140,7 +140,8 @@ void ModuleMap::FetchSingleModuleScript(const ModuleScriptFetchRequest& request,
// with moduleMap[url], and abort these steps." [spec text]
// Step 11. "Set moduleMap[url] to module script, and asynchronously complete
// this algorithm with module script." [spec text]
entry->AddClient(client);
if (client)
entry->AddClient(client);
}
ModuleScript* ModuleMap::GetFetchedModuleScript(const KURL& url) const {
......
......@@ -175,7 +175,8 @@ TEST_F(ModuleMapTest, sequentialRequests) {
platform->AdvanceClockSeconds(1.); // For non-zero DocumentParserTimings
KURL url(NullURL(), "https://example.com/foo.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
// First request
TestSingleModuleClient* client = new TestSingleModuleClient;
......@@ -217,7 +218,8 @@ TEST_F(ModuleMapTest, concurrentRequestsShouldJoin) {
platform->AdvanceClockSeconds(1.); // For non-zero DocumentParserTimings
KURL url(NullURL(), "https://example.com/foo.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
// First request
TestSingleModuleClient* client = new TestSingleModuleClient;
......
......@@ -222,6 +222,21 @@ bool ScriptLoader::BlockForNoModule(ScriptType script_type, bool nomodule) {
return nomodule && script_type == ScriptType::kClassic;
}
// Step 16 of https://html.spec.whatwg.org/#prepare-a-script
network::mojom::FetchCredentialsMode ScriptLoader::ModuleScriptCredentialsMode(
CrossOriginAttributeValue cross_origin) {
switch (cross_origin) {
case kCrossOriginAttributeNotSet:
return network::mojom::FetchCredentialsMode::kOmit;
case kCrossOriginAttributeAnonymous:
return network::mojom::FetchCredentialsMode::kSameOrigin;
case kCrossOriginAttributeUseCredentials:
return network::mojom::FetchCredentialsMode::kInclude;
}
NOTREACHED();
return network::mojom::FetchCredentialsMode::kOmit;
}
bool ScriptLoader::IsScriptTypeSupported(LegacyTypeSupport support_legacy_types,
ScriptType& out_script_type) const {
return IsValidScriptTypeAndLanguage(element_->TypeAttributeValue(),
......@@ -321,18 +336,7 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
// 16. "Let module script credentials mode be determined by switching
// on CORS setting:"
network::mojom::FetchCredentialsMode credentials_mode =
network::mojom::FetchCredentialsMode::kOmit;
switch (cross_origin) {
case kCrossOriginAttributeNotSet:
credentials_mode = network::mojom::FetchCredentialsMode::kOmit;
break;
case kCrossOriginAttributeAnonymous:
credentials_mode = network::mojom::FetchCredentialsMode::kSameOrigin;
break;
case kCrossOriginAttributeUseCredentials:
credentials_mode = network::mojom::FetchCredentialsMode::kInclude;
break;
}
ModuleScriptCredentialsMode(cross_origin);
// 17. "If the script element has a nonce attribute,
// then let cryptographic nonce be that attribute's value.
......@@ -697,8 +701,9 @@ void ScriptLoader::FetchModuleScriptTree(const KURL& url,
// options."
auto* module_tree_client = ModulePendingScriptTreeClient::Create();
modulator->FetchTree(ModuleScriptFetchRequest(url, options),
module_tree_client);
modulator->FetchTree(
ModuleScriptFetchRequest(url, modulator->GetReferrerPolicy(), options),
module_tree_client);
prepared_pending_script_ = ModulePendingScript::Create(
element_, module_tree_client, is_external_script_);
}
......
......@@ -75,6 +75,9 @@ class CORE_EXPORT ScriptLoader : public GarbageCollectedFinalized<ScriptLoader>,
static bool BlockForNoModule(ScriptType, bool nomodule);
static network::mojom::FetchCredentialsMode ModuleScriptCredentialsMode(
CrossOriginAttributeValue);
// https://html.spec.whatwg.org/#prepare-a-script
bool PrepareScript(const TextPosition& script_start_position =
TextPosition::MinimumPosition(),
......
......@@ -31,6 +31,8 @@
#include "core/html/LinkRelAttribute.h"
#include "platform/runtime_enabled_features.h"
namespace blink {
LinkRelAttribute::LinkRelAttribute()
......@@ -45,6 +47,7 @@ LinkRelAttribute::LinkRelAttribute()
is_link_next_(false),
is_import_(false),
is_manifest_(false),
is_module_preload_(false),
is_service_worker_(false),
is_canonical_(false) {}
......@@ -90,6 +93,9 @@ LinkRelAttribute::LinkRelAttribute(const String& rel) : LinkRelAttribute() {
icon_type_ = kTouchPrecomposedIcon;
} else if (DeprecatedEqualIgnoringCase(link_type, "manifest")) {
is_manifest_ = true;
} else if (DeprecatedEqualIgnoringCase(link_type, "modulepreload")) {
if (RuntimeEnabledFeatures::ModulePreloadEnabled())
is_module_preload_ = true;
} else if (DeprecatedEqualIgnoringCase(link_type, "serviceworker")) {
is_service_worker_ = true;
} else if (DeprecatedEqualIgnoringCase(link_type, "canonical")) {
......
......@@ -57,6 +57,7 @@ class CORE_EXPORT LinkRelAttribute {
bool IsLinkNext() const { return is_link_next_; }
bool IsImport() const { return is_import_; }
bool IsManifest() const { return is_manifest_; }
bool IsModulePreload() const { return is_module_preload_; }
bool IsServiceWorker() const { return is_service_worker_; }
bool IsCanonical() const { return is_canonical_; }
......@@ -72,6 +73,7 @@ class CORE_EXPORT LinkRelAttribute {
bool is_link_next_ : 1;
bool is_import_ : 1;
bool is_manifest_ : 1;
bool is_module_preload_ : 1;
bool is_service_worker_ : 1;
bool is_canonical_ : 1;
};
......
......@@ -8,6 +8,7 @@
#include "core/dom/Element.h"
#include "core/html_names.h"
#include "core/origin_trials/origin_trials.h"
#include "platform/runtime_enabled_features.h"
#include "platform/wtf/HashMap.h"
namespace blink {
......@@ -31,9 +32,14 @@ bool RelList::ValidateTokenValue(const AtomicString& token_value,
ExceptionState&) const {
if (SupportedTokens().Contains(token_value))
return true;
return OriginTrials::linkServiceWorkerEnabled(
GetElement().GetExecutionContext()) &&
token_value == "serviceworker";
if (OriginTrials::linkServiceWorkerEnabled(
GetElement().GetExecutionContext()) &&
token_value == "serviceworker")
return true;
if (RuntimeEnabledFeatures::ModulePreloadEnabled() &&
token_value == "modulepreload")
return true;
return false;
}
} // namespace blink
......@@ -121,6 +121,7 @@ class TokenPreloadScanner::StartTagScanner {
link_is_style_sheet_(false),
link_is_preconnect_(false),
link_is_preload_(false),
link_is_modulepreload_(false),
link_is_import_(false),
matched_(true),
input_is_image_(false),
......@@ -199,6 +200,9 @@ class TokenPreloadScanner::StartTagScanner {
type = ResourceTypeForLinkPreload();
if (type == WTF::nullopt)
return nullptr;
} else if (IsLinkRelModulePreload()) {
request_type = PreloadRequest::kRequestTypeLinkRelPreload;
type = Resource::kScript;
}
if (!ShouldPreload(type)) {
return nullptr;
......@@ -239,10 +243,9 @@ class TokenPreloadScanner::StartTagScanner {
if (!request)
return nullptr;
if (Match(tag_impl_, scriptTag)) {
request->SetScriptType(type_attribute_value_ == "module"
? ScriptType::kModule
: ScriptType::kClassic);
if ((Match(tag_impl_, scriptTag) && type_attribute_value_ == "module") ||
IsLinkRelModulePreload()) {
request->SetScriptType(ScriptType::kModule);
}
request->SetCrossOrigin(cross_origin_);
......@@ -343,6 +346,7 @@ class TokenPreloadScanner::StartTagScanner {
!rel.IsDNSPrefetch();
link_is_preconnect_ = rel.IsPreconnect();
link_is_preload_ = rel.IsLinkPreload();
link_is_modulepreload_ = rel.IsModulePreload();
link_is_import_ = rel.IsImport();
} else if (Match(attribute_name, mediaAttr)) {
matched_ &= MediaAttributeMatches(*media_values_, attribute_value);
......@@ -485,6 +489,11 @@ class TokenPreloadScanner::StartTagScanner {
!url_to_load_.IsEmpty();
}
bool IsLinkRelModulePreload() const {
return Match(tag_impl_, linkTag) && link_is_modulepreload_ &&
!url_to_load_.IsEmpty();
}
bool ShouldPreloadLink(WTF::Optional<Resource::Type>& type) const {
if (link_is_style_sheet_) {
return type_attribute_value_.IsEmpty() ||
......@@ -504,6 +513,8 @@ class TokenPreloadScanner::StartTagScanner {
type_from_attribute))) {
return false;
}
} else if (link_is_modulepreload_) {
return true;
} else if (!link_is_import_) {
return false;
}
......@@ -552,6 +563,7 @@ class TokenPreloadScanner::StartTagScanner {
bool link_is_style_sheet_;
bool link_is_preconnect_;
bool link_is_preload_;
bool link_is_modulepreload_;
bool link_is_import_;
bool matched_;
bool input_is_image_;
......
......@@ -6,6 +6,7 @@
#include "core/dom/Document.h"
#include "core/dom/DocumentWriteIntervention.h"
#include "core/dom/ScriptLoader.h"
#include "core/loader/DocumentLoader.h"
#include "platform/CrossOriginAttributeValue.h"
#include "platform/loader/fetch/FetchInitiatorInfo.h"
......@@ -62,21 +63,9 @@ Resource* PreloadRequest::Start(Document* document) {
if (script_type_ == ScriptType::kModule) {
DCHECK_EQ(resource_type_, Resource::kScript);
network::mojom::FetchCredentialsMode credentials_mode =
network::mojom::FetchCredentialsMode::kOmit;
switch (cross_origin_) {
case kCrossOriginAttributeNotSet:
credentials_mode = network::mojom::FetchCredentialsMode::kOmit;
break;
case kCrossOriginAttributeAnonymous:
credentials_mode = network::mojom::FetchCredentialsMode::kSameOrigin;
break;
case kCrossOriginAttributeUseCredentials:
credentials_mode = network::mojom::FetchCredentialsMode::kInclude;
break;
}
params.SetCrossOriginAccessControl(document->GetSecurityOrigin(),
credentials_mode);
params.SetCrossOriginAccessControl(
document->GetSecurityOrigin(),
ScriptLoader::ModuleScriptCredentialsMode(cross_origin_));
} else if (cross_origin_ != kCrossOriginAttributeNotSet) {
params.SetCrossOriginAccessControl(document->GetSecurityOrigin(),
cross_origin_);
......
......@@ -31,9 +31,12 @@
#include "core/loader/LinkLoader.h"
#include "bindings/core/v8/V8BindingForCore.h"
#include "core/css/MediaList.h"
#include "core/css/MediaQueryEvaluator.h"
#include "core/dom/Document.h"
#include "core/dom/ModuleScript.h"
#include "core/dom/ScriptLoader.h"
#include "core/frame/FrameConsole.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
......@@ -44,6 +47,7 @@
#include "core/inspector/ConsoleMessage.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/NetworkHintsInterface.h"
#include "core/loader/modulescript/ModuleScriptFetchRequest.h"
#include "core/loader/private/PrerenderHandle.h"
#include "core/loader/resource/LinkFetchResource.h"
#include "platform/Prerender.h"
......@@ -135,6 +139,13 @@ void LinkLoader::NotifyFinished() {
client_->LinkLoaded();
}
void LinkLoader::NotifyModuleLoadFinished(ModuleScript* module) {
if (!module || module->IsErrored())
client_->LinkLoadingErrored();
else
client_->LinkLoaded();
}
void LinkLoader::DidStartPrerender() {
client_->DidStartLinkPrerender();
}
......@@ -287,6 +298,24 @@ static bool IsSupportedType(Resource::Type resource_type,
return false;
}
static bool MediaMatches(Document& document,
const String& media,
ViewportDescription* viewport_description) {
if (media.IsEmpty())
return true;
MediaValues* media_values =
MediaValues::CreateDynamicIfFrameExists(document.GetFrame());
if (viewport_description) {
media_values->OverrideViewportDimensions(
viewport_description->max_width.GetFloatValue(),
viewport_description->max_height.GetFloatValue());
}
scoped_refptr<MediaQuerySet> media_queries = MediaQuerySet::Create(media);
MediaQueryEvaluator evaluator(*media_values);
return evaluator.Eval(*media_queries);
}
static Resource* PreloadIfNeeded(const LinkRelAttribute& rel_attribute,
const KURL& href,
Document& document,
......@@ -309,21 +338,10 @@ static Resource* PreloadIfNeeded(const LinkRelAttribute& rel_attribute,
return nullptr;
}
if (!media.IsEmpty()) {
MediaValues* media_values =
MediaValues::CreateDynamicIfFrameExists(document.GetFrame());
if (viewport_description) {
media_values->OverrideViewportDimensions(
viewport_description->max_width.GetFloatValue(),
viewport_description->max_height.GetFloatValue());
}
// Preload only if media matches
if (!MediaMatches(document, media, viewport_description))
return nullptr;
// Preload only if media matches
scoped_refptr<MediaQuerySet> media_queries = MediaQuerySet::Create(media);
MediaQueryEvaluator evaluator(*media_values);
if (!evaluator.Eval(*media_queries))
return nullptr;
}
if (caller == kLinkCalledFromHeader)
UseCounter::Count(document, WebFeature::kLinkHeaderPreload);
Optional<Resource::Type> resource_type =
......@@ -371,6 +389,69 @@ static Resource* PreloadIfNeeded(const LinkRelAttribute& rel_attribute,
link_fetch_params);
}
static void ModulePreloadIfNeeded(const LinkRelAttribute& rel_attribute,
const KURL& href,
Document& document,
const String& media,
const String& nonce,
CrossOriginAttributeValue cross_origin,
ViewportDescription* viewport_description,
ReferrerPolicy referrer_policy,
SingleModuleClient* client) {
if (!document.Loader() || !rel_attribute.IsModulePreload())
return;
// TODO(ksakamoto): add UseCounter
if (href.IsEmpty()) {
document.AddConsoleMessage(
ConsoleMessage::Create(kOtherMessageSource, kWarningMessageLevel,
"<link rel=modulepreload> has no `href` value"));
return;
}
if (!href.IsValid()) {
document.AddConsoleMessage(ConsoleMessage::Create(
kOtherMessageSource, kWarningMessageLevel,
"<link rel=modulepreload> has an invalid `href` value " +
href.GetString()));
return;
}
// Preload only if media matches
if (!MediaMatches(document, media, viewport_description))
return;
// https://github.com/whatwg/html/pull/2383
// 5. Let settings object be the link element's node document's relevant
// settings object.
// |document| is the node document here, and its context document is the
// relevant settings object.
Document* context_document = document.ContextDocument();
Modulator* modulator =
Modulator::From(ToScriptStateForMainWorld(context_document->GetFrame()));
DCHECK(modulator);
if (!modulator)
return;
network::mojom::FetchCredentialsMode credentials_mode =
ScriptLoader::ModuleScriptCredentialsMode(cross_origin);
ModuleScriptFetchRequest request(
href, referrer_policy,
ScriptFetchOptions(nonce, IntegrityMetadataSet(), String(),
kParserInserted, credentials_mode));
modulator->FetchSingle(request, ModuleGraphLevel::kDependentModuleFetch,
client);
Settings* settings = document.GetSettings();
if (settings && settings->GetLogPreload()) {
document.AddConsoleMessage(ConsoleMessage::Create(
kOtherMessageSource, kVerboseMessageLevel,
"Module preload triggered for " + href.Host() + href.GetPath()));
}
}
static Resource* PrefetchIfNeeded(Document& document,
const KURL& href,
const LinkRelAttribute& rel_attribute,
......@@ -447,6 +528,9 @@ void LinkLoader::LoadLinksFromHeader(
kReferrerPolicyDefault);
PrefetchIfNeeded(*document, url, rel_attribute, cross_origin,
kReferrerPolicyDefault);
ModulePreloadIfNeeded(rel_attribute, url, *document, header.Media(),
header.Nonce(), cross_origin, viewport_description,
kReferrerPolicyDefault, nullptr);
}
if (rel_attribute.IsServiceWorker()) {
UseCounter::Count(&frame, WebFeature::kLinkHeaderServiceWorker);
......@@ -489,6 +573,9 @@ bool LinkLoader::LoadLink(
if (resource)
finish_observer_ = new FinishObserver(this, resource);
ModulePreloadIfNeeded(rel_attribute, href, document, media, nonce,
cross_origin, nullptr, referrer_policy, this);
if (const unsigned prerender_rel_types =
PrerenderRelTypesFromRelAttribute(rel_attribute, document)) {
if (!prerender_) {
......@@ -522,6 +609,7 @@ void LinkLoader::Trace(blink::Visitor* visitor) {
visitor->Trace(finish_observer_);
visitor->Trace(client_);
visitor->Trace(prerender_);
SingleModuleClient::Trace(visitor);
PrerenderClient::Trace(visitor);
}
......
......@@ -33,6 +33,7 @@
#define LinkLoader_h
#include "core/CoreExport.h"
#include "core/dom/Modulator.h"
#include "core/loader/LinkLoaderClient.h"
#include "platform/CrossOriginAttributeValue.h"
#include "platform/PrerenderClient.h"
......@@ -50,9 +51,8 @@ struct ViewportDescriptionWrapper;
// The LinkLoader can load link rel types icon, dns-prefetch, prefetch, and
// prerender.
class CORE_EXPORT LinkLoader final
: public GarbageCollectedFinalized<LinkLoader>,
public PrerenderClient {
class CORE_EXPORT LinkLoader final : public SingleModuleClient,
public PrerenderClient {
USING_GARBAGE_COLLECTED_MIXIN(LinkLoader);
public:
......@@ -106,6 +106,8 @@ class CORE_EXPORT LinkLoader final
LinkLoader(LinkLoaderClient*, scoped_refptr<WebTaskRunner>);
void NotifyFinished();
// SingleModuleClient implementation
void NotifyModuleLoadFinished(ModuleScript*) override;
Member<FinishObserver> finish_observer_;
Member<LinkLoaderClient> client_;
......
......@@ -6,11 +6,14 @@
#include <base/macros.h>
#include <memory>
#include "bindings/core/v8/V8BindingForCore.h"
#include "core/frame/Settings.h"
#include "core/html/LinkRelAttribute.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/LinkLoaderClient.h"
#include "core/loader/NetworkHintsInterface.h"
#include "core/loader/modulescript/ModuleScriptFetchRequest.h"
#include "core/testing/DummyModulator.h"
#include "core/testing/DummyPageHolder.h"
#include "platform/loader/fetch/MemoryCache.h"
#include "platform/loader/fetch/ResourceFetcher.h"
......@@ -318,6 +321,79 @@ INSTANTIATE_TEST_CASE_P(LinkLoaderPreloadTest,
LinkLoaderPreloadTest,
::testing::ValuesIn(kPreloadTestParams));
struct ModulePreloadTestParams {
const char* href;
const char* nonce;
CrossOriginAttributeValue cross_origin;
ReferrerPolicy referrer_policy;
bool expecting_load;
network::mojom::FetchCredentialsMode expected_credentials_mode;
};
constexpr ModulePreloadTestParams kModulePreloadTestParams[] = {
{"", nullptr, kCrossOriginAttributeNotSet, kReferrerPolicyDefault, false,
network::mojom::FetchCredentialsMode::kOmit},
{"http://example.test/cat.js", nullptr, kCrossOriginAttributeNotSet,
kReferrerPolicyDefault, true, network::mojom::FetchCredentialsMode::kOmit},
{"http://example.test/cat.js", nullptr, kCrossOriginAttributeAnonymous,
kReferrerPolicyDefault, true,
network::mojom::FetchCredentialsMode::kSameOrigin},
{"http://example.test/cat.js", "nonce", kCrossOriginAttributeNotSet,
kReferrerPolicyNever, true, network::mojom::FetchCredentialsMode::kOmit}};
class LinkLoaderModulePreloadTest
: public ::testing::TestWithParam<ModulePreloadTestParams> {};
class ModulePreloadTestModulator final : public DummyModulator {
public:
ModulePreloadTestModulator(const ModulePreloadTestParams* params)
: params_(params), fetched_(false) {}
void FetchSingle(const ModuleScriptFetchRequest& request,
ModuleGraphLevel,
SingleModuleClient*) override {
fetched_ = true;
EXPECT_EQ(KURL(NullURL(), params_->href), request.Url());
EXPECT_EQ(params_->nonce, request.Options().Nonce());
EXPECT_EQ(kParserInserted, request.Options().ParserState());
EXPECT_EQ(params_->expected_credentials_mode,
request.Options().CredentialsMode());
EXPECT_EQ(AtomicString(), request.GetReferrer());
EXPECT_EQ(params_->referrer_policy, request.GetReferrerPolicy());
}
bool fetched() const { return fetched_; }
private:
const ModulePreloadTestParams* params_;
bool fetched_;
};
TEST_P(LinkLoaderModulePreloadTest, ModulePreload) {
const auto& test_case = GetParam();
std::unique_ptr<DummyPageHolder> dummy_page_holder =
DummyPageHolder::Create();
ModulePreloadTestModulator* modulator =
new ModulePreloadTestModulator(&test_case);
Modulator::SetModulator(
ToScriptStateForMainWorld(dummy_page_holder->GetDocument().GetFrame()),
modulator);
Persistent<MockLinkLoaderClient> loader_client =
MockLinkLoaderClient::Create(true);
LinkLoader* loader = LinkLoader::Create(loader_client.Get());
KURL href_url = KURL(NullURL(), test_case.href);
loader->LoadLink(LinkRelAttribute("modulepreload"), test_case.cross_origin,
String() /* type */, String() /* as */, String() /* media */,
test_case.nonce, test_case.referrer_policy, href_url,
dummy_page_holder->GetDocument(), NetworkHintsMock());
ASSERT_EQ(test_case.expecting_load, modulator->fetched());
}
INSTANTIATE_TEST_CASE_P(LinkLoaderModulePreloadTest,
LinkLoaderModulePreloadTest,
::testing::ValuesIn(kModulePreloadTestParams));
TEST(LinkLoaderTest, Prefetch) {
struct TestCase {
const char* href;
......
......@@ -18,16 +18,20 @@ class ModuleScriptFetchRequest final {
STACK_ALLOCATED();
public:
ModuleScriptFetchRequest(const KURL& url, const ScriptFetchOptions& options)
ModuleScriptFetchRequest(const KURL& url,
ReferrerPolicy referrer_policy,
const ScriptFetchOptions& options)
: ModuleScriptFetchRequest(url,
options,
Referrer::NoReferrer(),
referrer_policy,
TextPosition::MinimumPosition()) {}
~ModuleScriptFetchRequest() = default;
const KURL& Url() const { return url_; }
const ScriptFetchOptions& Options() const { return options_; }
const AtomicString& GetReferrer() const { return referrer_; }
ReferrerPolicy GetReferrerPolicy() const { return referrer_policy_; }
const TextPosition& GetReferrerPosition() const { return referrer_position_; }
private:
......@@ -37,15 +41,18 @@ class ModuleScriptFetchRequest final {
ModuleScriptFetchRequest(const KURL& url,
const ScriptFetchOptions& options,
const String& referrer,
ReferrerPolicy referrer_policy,
const TextPosition& referrer_position)
: url_(url),
options_(options),
referrer_(referrer),
referrer_policy_(referrer_policy),
referrer_position_(referrer_position) {}
const KURL url_;
const ScriptFetchOptions options_;
const AtomicString referrer_;
const ReferrerPolicy referrer_policy_;
const TextPosition referrer_position_;
};
......
......@@ -137,7 +137,7 @@ void ModuleScriptLoader::Fetch(const ModuleScriptFetchRequest& module_request,
// Step 5. "... referrer is referrer, ..." [spec text]
if (!module_request.GetReferrer().IsNull()) {
resource_request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer(
modulator_->GetReferrerPolicy(), module_request.Url(),
module_request.GetReferrerPolicy(), module_request.Url(),
module_request.GetReferrer()));
}
// Step 5. "... and client is fetch client settings object." [spec text]
......
......@@ -204,7 +204,8 @@ void ModuleScriptLoaderTest::TestFetchDataURL(
TestModuleScriptLoaderClient* client) {
ModuleScriptLoaderRegistry* registry = ModuleScriptLoaderRegistry::Create();
KURL url("data:text/javascript,export default 'grapes';");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
registry->Fetch(module_request, ModuleGraphLevel::kTopLevelModuleFetch,
GetModulator(), client);
}
......@@ -251,7 +252,8 @@ void ModuleScriptLoaderTest::TestInvalidSpecifier(
TestModuleScriptLoaderClient* client) {
ModuleScriptLoaderRegistry* registry = ModuleScriptLoaderRegistry::Create();
KURL url("data:text/javascript,import 'invalid';export default 'grapes';");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
GetModulator()->SetModuleRequests({"invalid"});
registry->Fetch(module_request, ModuleGraphLevel::kTopLevelModuleFetch,
GetModulator(), client);
......@@ -287,7 +289,8 @@ void ModuleScriptLoaderTest::TestFetchInvalidURL(
ModuleScriptLoaderRegistry* registry = ModuleScriptLoaderRegistry::Create();
KURL url;
EXPECT_FALSE(url.IsValid());
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
registry->Fetch(module_request, ModuleGraphLevel::kTopLevelModuleFetch,
GetModulator(), client);
}
......@@ -322,7 +325,8 @@ void ModuleScriptLoaderTest::TestFetchURL(
url, testing::CoreTestDataPath("module.js"), "text/javascript");
ModuleScriptLoaderRegistry* registry = ModuleScriptLoaderRegistry::Create();
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
registry->Fetch(module_request, ModuleGraphLevel::kTopLevelModuleFetch,
GetModulator(), client);
}
......
......@@ -363,7 +363,8 @@ void ModuleTreeLinker::FetchDescendants(ModuleScript* module_script) {
// [FD] Step 7. ... perform the internal module script graph fetching
// procedure given ... with the top-level module fetch flag unset. ...
ModuleScriptFetchRequest request(
urls[i], options, module_script->BaseURL().GetString(), positions[i]);
urls[i], options, module_script->BaseURL().GetString(),
modulator_->GetReferrerPolicy(), positions[i]);
InitiateInternalModuleScriptGraphFetching(
request, ModuleGraphLevel::kDependentModuleFetch);
}
......
......@@ -123,6 +123,7 @@ class ModuleTreeLinkerTestModulator final : public DummyModulator {
private:
// Implements Modulator:
ReferrerPolicy GetReferrerPolicy() override { return kReferrerPolicyDefault; }
ScriptState* GetScriptState() override { return script_state_.get(); }
void FetchSingle(const ModuleScriptFetchRequest& request,
......@@ -218,7 +219,8 @@ TEST_F(ModuleTreeLinkerTest, FetchTreeNoDeps) {
ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create();
KURL url("http://example.com/root.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
TestModuleTreeClient* client = new TestModuleTreeClient;
registry->Fetch(module_request, GetModulator(), client);
......@@ -239,7 +241,8 @@ TEST_F(ModuleTreeLinkerTest, FetchTreeInstantiationFailure) {
ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create();
KURL url("http://example.com/root.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
TestModuleTreeClient* client = new TestModuleTreeClient;
registry->Fetch(module_request, GetModulator(), client);
......@@ -264,7 +267,8 @@ TEST_F(ModuleTreeLinkerTest, FetchTreePreviousInstantiationFailure) {
ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create();
KURL url("http://example.com/root.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
TestModuleTreeClient* client = new TestModuleTreeClient;
registry->Fetch(module_request, GetModulator(), client);
......@@ -285,7 +289,8 @@ TEST_F(ModuleTreeLinkerTest, FetchTreeWithSingleDependency) {
ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create();
KURL url("http://example.com/root.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
TestModuleTreeClient* client = new TestModuleTreeClient;
registry->Fetch(module_request, GetModulator(), client);
......@@ -311,7 +316,8 @@ TEST_F(ModuleTreeLinkerTest, FetchTreeWith3Deps) {
ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create();
KURL url("http://example.com/root.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
TestModuleTreeClient* client = new TestModuleTreeClient;
registry->Fetch(module_request, GetModulator(), client);
......@@ -350,7 +356,8 @@ TEST_F(ModuleTreeLinkerTest, FetchTreeWith3Deps1Fail) {
ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create();
KURL url("http://example.com/root.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
TestModuleTreeClient* client = new TestModuleTreeClient;
registry->Fetch(module_request, GetModulator(), client);
......@@ -406,7 +413,8 @@ TEST_F(ModuleTreeLinkerTest, FetchDependencyTree) {
ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create();
KURL url("http://example.com/depth1.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
TestModuleTreeClient* client = new TestModuleTreeClient;
registry->Fetch(module_request, GetModulator(), client);
......@@ -431,7 +439,8 @@ TEST_F(ModuleTreeLinkerTest, FetchDependencyOfCyclicGraph) {
ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create();
KURL url("http://example.com/a.js");
ModuleScriptFetchRequest module_request(url, ScriptFetchOptions());
ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault,
ScriptFetchOptions());
TestModuleTreeClient* client = new TestModuleTreeClient;
registry->Fetch(module_request, GetModulator(), client);
......
......@@ -110,7 +110,8 @@ void WorkletGlobalScope::FetchAndInvokeScript(
Modulator* modulator = Modulator::From(ScriptController()->GetScriptState());
// [FMWST] Step 3. "Perform the internal module script graph fetching
// procedure ..."
ModuleScriptFetchRequest module_request(module_url_record, options);
ModuleScriptFetchRequest module_request(
module_url_record, modulator->GetReferrerPolicy(), options);
// Step 3 to 5 are implemented in
// WorkletModuleTreeClient::NotifyModuleTreeLoadFinished.
......
......@@ -642,6 +642,10 @@
{
name: "ModernMediaControls",
},
{
name: "ModulePreload",
status: "experimental",
},
{
name: "ModuleScriptsDynamicImport",
},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment