Bug 1798319 - Implement modulepreload in early hints r=manuel,smaug,necko-reviewers,kershaw

The aEarlyHintPreloaderId parameter for StartLoad/StartLoadInternal is changed
to be a member variable of ScriptLoadRequest instead so that an initiator type
of early hints can be set for module requests. Before, ModuleLoader would always
pass in a zero value for the id since ModuleLoaderBase has no concept of early
hints when it calls StartFetch.

As a prerequisite for early hints support, this commit also implements
modulepreload in link headers (Bug 1773056).

Differential Revision: https://phabricator.services.mozilla.com/D180020
This commit is contained in:
Em Zhan 2023-06-26 10:49:53 +00:00
parent c92d9850e8
commit b6b6fe577b
19 changed files with 204 additions and 63 deletions

View file

@ -14,6 +14,7 @@
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_content.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/LinkStyle.h"
#include "mozilla/dom/ReferrerInfo.h"
@ -57,6 +58,14 @@
#include "nsSandboxFlags.h"
#include "Link.h"
#include "HTMLLinkElement.h"
#include "MediaList.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include <stdint.h>
#include "mozilla/RefPtr.h"
#include "nsCOMPtr.h"
#include "nsLiteralString.h"
#include "nsIContentPolicy.h"
using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;
@ -309,9 +318,9 @@ nsresult nsContentSink::ProcessLinkFromHeader(const net::LinkHeader& aHeader,
if ((linkTypes & LinkStyle::eMODULE_PRELOAD) &&
mDocument->ScriptLoader()->GetModuleLoader()) {
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-modulepreload-module-script-graph
// Step 1. Disallow further import maps given settings object.
mDocument->ScriptLoader()->GetModuleLoader()->DisallowImportMaps();
PreloadModule(aHeader.mHref, aHeader.mAs, aHeader.mMedia,
aHeader.mIntegrity, aHeader.mCrossOrigin,
aHeader.mReferrerPolicy, aEarlyHintPreloaderId);
}
}
@ -444,6 +453,50 @@ void nsContentSink::PreloadHref(const nsAString& aHref, const nsAString& aAs,
aReferrerPolicy, aEarlyHintPreloaderId);
}
void nsContentSink::PreloadModule(const nsAString& aHref, const nsAString& aAs,
const nsAString& aMedia,
const nsAString& aIntegrity,
const nsAString& aCORS,
const nsAString& aReferrerPolicy,
uint64_t aEarlyHintPreloaderId) {
ModuleLoader* moduleLoader = mDocument->ScriptLoader()->GetModuleLoader();
if (!StaticPrefs::network_modulepreload()) {
// Keep behavior from https://phabricator.services.mozilla.com/D149371,
// prior to main implementation of modulepreload
moduleLoader->DisallowImportMaps();
return;
}
RefPtr<mozilla::dom::MediaList> mediaList =
mozilla::dom::MediaList::Create(NS_ConvertUTF16toUTF8(aMedia));
if (!mediaList->Matches(*mDocument)) {
return;
}
if (aHref.IsEmpty()) {
return;
}
if (!net::IsScriptLikeOrInvalid(aAs)) {
return;
}
auto encoding = mDocument->GetDocumentCharacterSet();
nsCOMPtr<nsIURI> uri;
NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
if (!uri) {
return;
}
moduleLoader->DisallowImportMaps();
mDocument->Preloads().PreloadLinkHeader(
uri, aHref, nsIContentPolicy::TYPE_SCRIPT, u"script"_ns, u"module"_ns,
aIntegrity, u""_ns, u""_ns, aCORS, aReferrerPolicy,
aEarlyHintPreloaderId);
}
void nsContentSink::PrefetchDNS(const nsAString& aHref) {
nsAutoString hostname;
bool isHttps = false;

View file

@ -142,6 +142,11 @@ class nsContentSink : public nsICSSLoaderObserver,
const nsAString& aReferrerPolicy,
uint64_t aEarlyHintPreloaderId);
void PreloadModule(const nsAString& aHref, const nsAString& aAs,
const nsAString& aMedia, const nsAString& aIntegrity,
const nsAString& aCORS, const nsAString& aReferrerPolicy,
uint64_t aEarlyHintPreloaderId);
// For PrefetchDNS() aHref can either be the usual
// URI format or of the form "//www.hostname.com" without a scheme.
void PrefetchDNS(const nsAString& aHref);

View file

@ -32,6 +32,7 @@
#include "nsGlobalWindowInner.h"
#include "nsIPrincipal.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/Maybe.h"
using JS::SourceText;
using namespace JS::loader;
@ -112,7 +113,7 @@ nsresult ModuleLoader::StartFetch(ModuleLoadRequest* aRequest) {
// and `StartLoadInternal` is able to find the charset by using `aRequest`
// for this case.
nsresult rv = GetScriptLoader()->StartLoadInternal(
aRequest, securityFlags, 0, Nothing() /* aCharsetForPreload */);
aRequest, securityFlags, Nothing() /* aCharsetForPreload */);
NS_ENSURE_SUCCESS(rv, rv);
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-import()-module-script-graph

View file

@ -97,6 +97,8 @@
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "nsIScriptError.h"
#include "nsIAsyncOutputStream.h"
#include "js/loader/ModuleLoaderBase.h"
#include "mozilla/Maybe.h"
using JS::SourceText;
using namespace JS::loader;
@ -526,7 +528,7 @@ nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) {
if (aRequest->IsModuleRequest()) {
rv = aRequest->AsModuleRequest()->RestartModuleLoad();
} else {
rv = StartLoad(aRequest, 0, Nothing());
rv = StartLoad(aRequest, Nothing());
}
if (NS_FAILED(rv)) {
return rv;
@ -538,17 +540,17 @@ nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) {
}
nsresult ScriptLoader::StartLoad(
ScriptLoadRequest* aRequest, uint64_t aEarlyHintPreloaderId,
ScriptLoadRequest* aRequest,
const Maybe<nsAutoString>& aCharsetForPreload) {
if (aRequest->IsModuleRequest()) {
return aRequest->AsModuleRequest()->StartModuleLoad();
}
return StartClassicLoad(aRequest, aEarlyHintPreloaderId, aCharsetForPreload);
return StartClassicLoad(aRequest, aCharsetForPreload);
}
nsresult ScriptLoader::StartClassicLoad(
ScriptLoadRequest* aRequest, uint64_t aEarlyHintPreloaderId,
ScriptLoadRequest* aRequest,
const Maybe<nsAutoString>& aCharsetForPreload) {
MOZ_ASSERT(aRequest->IsFetching());
NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
@ -573,8 +575,7 @@ nsresult ScriptLoader::StartClassicLoad(
securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
nsresult rv = StartLoadInternal(aRequest, securityFlags,
aEarlyHintPreloaderId, aCharsetForPreload);
nsresult rv = StartLoadInternal(aRequest, securityFlags, aCharsetForPreload);
NS_ENSURE_SUCCESS(rv, rv);
@ -593,7 +594,6 @@ static bool IsWebExtensionRequest(ScriptLoadRequest* aRequest) {
nsresult ScriptLoader::StartLoadInternal(
ScriptLoadRequest* aRequest, nsSecurityFlags securityFlags,
uint64_t aEarlyHintPreloaderId,
const Maybe<nsAutoString>& aCharsetForPreload) {
nsContentPolicyType contentPolicyType =
ScriptLoadRequestToContentPolicyType(aRequest);
@ -620,12 +620,13 @@ nsresult ScriptLoader::StartLoadInternal(
NS_ENSURE_SUCCESS(rv, rv);
if (aEarlyHintPreloaderId) {
if (aRequest->mEarlyHintPreloaderId) {
nsCOMPtr<nsIHttpChannelInternal> channelInternal =
do_QueryInterface(channel);
NS_ENSURE_TRUE(channelInternal != nullptr, NS_ERROR_FAILURE);
rv = channelInternal->SetEarlyHintPreloaderId(aEarlyHintPreloaderId);
rv = channelInternal->SetEarlyHintPreloaderId(
aRequest->mEarlyHintPreloaderId);
NS_ENSURE_SUCCESS(rv, rv);
}
@ -767,7 +768,7 @@ nsresult ScriptLoader::StartLoadInternal(
// Set the initiator type
nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
if (timedChannel) {
if (aEarlyHintPreloaderId) {
if (aRequest->mEarlyHintPreloaderId) {
timedChannel->SetInitiatorType(u"early-hints"_ns);
} else if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) {
timedChannel->SetInitiatorType(u"link"_ns);
@ -800,12 +801,13 @@ nsresult ScriptLoader::StartLoadInternal(
aRequest->GetScriptLoadContext()->IsLinkPreloadScript(),
aRequest->IsModuleRequest());
if (aEarlyHintPreloaderId) {
if (aRequest->mEarlyHintPreloaderId) {
nsCOMPtr<nsIHttpChannelInternal> channelInternal =
do_QueryInterface(channel);
NS_ENSURE_TRUE(channelInternal != nullptr, NS_ERROR_FAILURE);
rv = channelInternal->SetEarlyHintPreloaderId(aEarlyHintPreloaderId);
rv = channelInternal->SetEarlyHintPreloaderId(
aRequest->mEarlyHintPreloaderId);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = channel->AsyncOpen(loader);
@ -1031,7 +1033,7 @@ bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
LOG(("ScriptLoadRequest (%p): Created request for external script",
request.get()));
nsresult rv = StartLoad(request, 0, Nothing());
nsresult rv = StartLoad(request, Nothing());
if (NS_FAILED(rv)) {
ReportErrorToConsole(request, rv);
@ -3568,6 +3570,7 @@ void ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset,
request->GetScriptLoadContext()->mScriptFromHead = aScriptFromHead;
request->GetScriptLoadContext()->SetScriptMode(aDefer, aAsync, aLinkPreload);
request->GetScriptLoadContext()->SetIsPreloadRequest();
request->mEarlyHintPreloaderId = aEarlyHintPreloaderId;
if (LOG_ENABLED()) {
nsAutoCString url;
@ -3577,7 +3580,7 @@ void ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset,
}
nsAutoString charset(aCharset);
nsresult rv = StartLoad(request, aEarlyHintPreloaderId, Some(charset));
nsresult rv = StartLoad(request, Some(charset));
if (NS_FAILED(rv)) {
return;
}

View file

@ -506,14 +506,12 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface {
* Start a load for aRequest's URI.
*/
nsresult StartLoad(ScriptLoadRequest* aRequest,
uint64_t aEarlyHintPreloaderId,
const Maybe<nsAutoString>& aCharsetForPreload);
/**
* Start a load for a classic script URI.
* Sets up the necessary security flags before calling StartLoadInternal.
*/
nsresult StartClassicLoad(ScriptLoadRequest* aRequest,
uint64_t aEarlyHintPreloaderId,
const Maybe<nsAutoString>& aCharsetForPreload);
/**
@ -525,7 +523,6 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface {
*/
nsresult StartLoadInternal(ScriptLoadRequest* aRequest,
nsSecurityFlags securityFlags,
uint64_t aEarlyHintPreloaderId,
const Maybe<nsAutoString>& aCharsetForPreload);
/**

View file

@ -88,7 +88,8 @@ ScriptLoadRequest::ScriptLoadRequest(ScriptKind aKind, nsIURI* aURI,
mScriptBytecode(),
mBytecodeOffset(0),
mURI(aURI),
mLoadContext(aContext) {
mLoadContext(aContext),
mEarlyHintPreloaderId(0) {
MOZ_ASSERT(mFetchOptions);
if (mLoadContext) {
mLoadContext->SetRequest(this);

View file

@ -369,6 +369,11 @@ class ScriptLoadRequest
// LoadContext for augmenting the load depending on the loading
// context (DOM, Worker, etc.)
RefPtr<LoadContextBase> mLoadContext;
// EarlyHintRegistrar id to connect the http channel back to the preload, with
// a default of value of 0 indicating that this request is not an early hints
// preload.
uint64_t mEarlyHintPreloaderId;
};
class ScriptLoadRequestList : private mozilla::LinkedList<ScriptLoadRequest> {

View file

@ -11773,8 +11773,7 @@
mirror: always
# Enables `<link rel="modulepreload">` tag and `Link: rel=modulepreload`
# response header handling. The latter is not yet implemented, see:
# https://bugzilla.mozilla.org/show_bug.cgi?id=1773056.
# response header handling.
- name: network.modulepreload
type: RelaxedAtomicBool
value: true

View file

@ -131,15 +131,18 @@ EarlyHintPreloader::~EarlyHintPreloader() {
/* static */
Maybe<PreloadHashKey> EarlyHintPreloader::GenerateHashKey(
ASDestination aAs, nsIURI* aURI, nsIPrincipal* aPrincipal,
CORSMode aCorsMode, const nsAString& aType) {
CORSMode aCorsMode, bool aIsModulepreload) {
if (aIsModulepreload) {
return Some(PreloadHashKey::CreateAsScript(
aURI, aCorsMode, JS::loader::ScriptKind::eModule));
}
if (aAs == ASDestination::DESTINATION_FONT && aCorsMode != CORS_NONE) {
return Some(PreloadHashKey::CreateAsFont(aURI, aCorsMode));
}
if (aAs == ASDestination::DESTINATION_IMAGE) {
return Some(PreloadHashKey::CreateAsImage(aURI, aPrincipal, aCorsMode));
}
if (aAs == ASDestination::DESTINATION_SCRIPT &&
!aType.LowerCaseEqualsASCII("module")) {
if (aAs == ASDestination::DESTINATION_SCRIPT) {
return Some(PreloadHashKey::CreateAsScript(
aURI, aCorsMode, JS::loader::ScriptKind::eClassic));
}
@ -156,8 +159,7 @@ Maybe<PreloadHashKey> EarlyHintPreloader::GenerateHashKey(
/* static */
nsSecurityFlags EarlyHintPreloader::ComputeSecurityFlags(CORSMode aCORSMode,
ASDestination aAs,
bool aIsModule) {
ASDestination aAs) {
if (aAs == ASDestination::DESTINATION_FONT) {
return nsContentSecurityManager::ComputeSecurityFlags(
CORSMode::CORS_NONE,
@ -170,12 +172,6 @@ nsSecurityFlags EarlyHintPreloader::ComputeSecurityFlags(CORSMode aCORSMode,
nsILoadInfo::SEC_ALLOW_CHROME;
}
if (aAs == ASDestination::DESTINATION_SCRIPT) {
if (aIsModule) {
return nsContentSecurityManager::ComputeSecurityFlags(
aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
REQUIRE_CORS_CHECKS) |
nsILoadInfo::SEC_ALLOW_CHROME;
}
return nsContentSecurityManager::ComputeSecurityFlags(
aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS) |
@ -205,7 +201,8 @@ void EarlyHintPreloader::MaybeCreateAndInsertPreload(
nsIURI* aBaseURI, nsIPrincipal* aPrincipal,
nsICookieJarSettings* aCookieJarSettings,
const nsACString& aResponseReferrerPolicy, const nsACString& aCSPHeader,
uint64_t aBrowsingContextID, nsIInterfaceRequestor* aCallbacks) {
uint64_t aBrowsingContextID, nsIInterfaceRequestor* aCallbacks,
bool aIsModulepreload) {
nsAttrValue as;
ParseAsValue(aLinkHeader.mAs, as);
@ -217,7 +214,7 @@ void EarlyHintPreloader::MaybeCreateAndInsertPreload(
return;
}
if (as.GetEnumValue() == ASDestination::DESTINATION_INVALID) {
if (destination == ASDestination::DESTINATION_INVALID && !aIsModulepreload) {
// return early when it's definitly not an asset type we preload
// would be caught later as well, e.g. when creating the PreloadHashKey
return;
@ -242,8 +239,7 @@ void EarlyHintPreloader::MaybeCreateAndInsertPreload(
CORSMode corsMode = dom::Element::StringToCORSMode(aLinkHeader.mCrossOrigin);
Maybe<PreloadHashKey> hashKey =
GenerateHashKey(static_cast<ASDestination>(as.GetEnumValue()), uri,
aPrincipal, corsMode, aLinkHeader.mType);
GenerateHashKey(destination, uri, aPrincipal, corsMode, aIsModulepreload);
if (!hashKey) {
return;
}
@ -252,7 +248,12 @@ void EarlyHintPreloader::MaybeCreateAndInsertPreload(
return;
}
nsContentPolicyType contentPolicyType = AsValueToContentPolicy(as);
nsContentPolicyType contentPolicyType =
aIsModulepreload ? (IsScriptLikeOrInvalid(aLinkHeader.mAs)
? nsContentPolicyType::TYPE_SCRIPT
: nsContentPolicyType::TYPE_INVALID)
: AsValueToContentPolicy(as);
if (contentPolicyType == nsContentPolicyType::TYPE_INVALID) {
return;
}
@ -284,9 +285,29 @@ void EarlyHintPreloader::MaybeCreateAndInsertPreload(
RefPtr<EarlyHintPreloader> earlyHintPreloader = new EarlyHintPreloader();
nsSecurityFlags securityFlags = EarlyHintPreloader::ComputeSecurityFlags(
corsMode, static_cast<ASDestination>(as.GetEnumValue()),
aLinkHeader.mType.LowerCaseEqualsASCII("module"));
DebugOnly<bool> result =
aOngoingEarlyHints->Add(*hashKey, earlyHintPreloader);
MOZ_ASSERT(result);
// Security flags for modulepreload's request mode are computed here directly
// until full support for worker destinations can be added.
//
// Implements "To fetch a single module script,"
// Step 9. If destination is "worker", "sharedworker", or "serviceworker",
// and the top-level module fetch flag is set, then set request's
// mode to "same-origin".
nsSecurityFlags securityFlags =
aIsModulepreload
? ((aLinkHeader.mAs.LowerCaseEqualsASCII("worker") ||
aLinkHeader.mAs.LowerCaseEqualsASCII("sharedworker") ||
aLinkHeader.mAs.LowerCaseEqualsASCII("serviceworker"))
? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
: nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT) |
(corsMode == CORS_USE_CREDENTIALS
? nsILoadInfo::SEC_COOKIES_INCLUDE
: nsILoadInfo::SEC_COOKIES_SAME_ORIGIN) |
nsILoadInfo::SEC_ALLOW_CHROME
: EarlyHintPreloader::ComputeSecurityFlags(corsMode, destination);
// Verify that the resource should be loaded.
// This isn't the ideal way to test the resource against the CSP.
@ -360,10 +381,6 @@ void EarlyHintPreloader::MaybeCreateAndInsertPreload(
aCookieJarSettings, aBrowsingContextID, aCallbacks));
earlyHintPreloader->SetLinkHeader(aLinkHeader);
DebugOnly<bool> result =
aOngoingEarlyHints->Add(*hashKey, earlyHintPreloader);
MOZ_ASSERT(result);
}
nsresult EarlyHintPreloader::OpenChannel(

View file

@ -89,7 +89,8 @@ class EarlyHintPreloader final : public nsIStreamListener,
nsIURI* aBaseURI, nsIPrincipal* aPrincipal,
nsICookieJarSettings* aCookieJarSettings,
const nsACString& aReferrerPolicy, const nsACString& aCSPHeader,
uint64_t aBrowsingContextID, nsIInterfaceRequestor* aCallbacks);
uint64_t aBrowsingContextID, nsIInterfaceRequestor* aCallbacks,
bool aIsModulepreload);
// register Channel to EarlyHintRegistrar. Returns true and sets connect args
// if successful
@ -121,11 +122,10 @@ class EarlyHintPreloader final : public nsIStreamListener,
static Maybe<PreloadHashKey> GenerateHashKey(ASDestination aAs, nsIURI* aURI,
nsIPrincipal* aPrincipal,
CORSMode corsMode,
const nsAString& aType);
bool aIsModulepreload);
static nsSecurityFlags ComputeSecurityFlags(CORSMode aCORSMode,
ASDestination aAs,
bool aIsModule);
ASDestination aAs);
// call to start the preload
nsresult OpenChannel(nsIURI* aURI, nsIPrincipal* aPrincipal,

View file

@ -102,7 +102,13 @@ void EarlyHintsService::EarlyHint(const nsACString& aLinkHeader,
EarlyHintPreloader::MaybeCreateAndInsertPreload(
mOngoingEarlyHints, linkHeader, aBaseURI, principal,
cookieJarSettings, aReferrerPolicy, aCSPHeader,
loadInfo->GetBrowsingContextID(), aCallbacks);
loadInfo->GetBrowsingContextID(), aCallbacks, false);
} else if (linkHeader.mRel.LowerCaseEqualsLiteral("modulepreload")) {
mLinkType |= dom::LinkStyle::eMODULE_PRELOAD;
EarlyHintPreloader::MaybeCreateAndInsertPreload(
mOngoingEarlyHints, linkHeader, aBaseURI, principal,
cookieJarSettings, aReferrerPolicy, aCSPHeader,
loadInfo->GetBrowsingContextID(), aCallbacks, true);
}
}
}

View file

@ -1,3 +0,0 @@
[modulepreload-in-early-hints.h2.window.html]
[Modulepreload in an early hints.]
expected: FAIL

View file

@ -1,4 +0,0 @@
[link-header-modulepreload.html]
expected: TIMEOUT
[test that a header-preloaded module is loaded and consumed]
expected: TIMEOUT

View file

@ -0,0 +1,15 @@
// META: script=/common/utils.js
// META: script=resources/early-hints-helpers.sub.js
// see modulepreload-in-early-hints.h2.window.js for params explanation
test(() => {
const params = new URLSearchParams();
params.set("description",
'Modulepreload should not load with as="worker" from cross-origin url');
params.set("resource-url",
CROSS_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
params.set("as", "worker");
params.set("should-preload", false);
const test_url = "resources/modulepreload-in-early-hints.h2.py?" + params.toString();
window.location.replace(new URL(test_url, window.location));
});

View file

@ -0,0 +1,15 @@
// META: script=/common/utils.js
// META: script=resources/early-hints-helpers.sub.js
// see modulepreload-in-early-hints.h2.window.js for params explanation
test(() => {
const params = new URLSearchParams();
params.set("description",
'Modulepreload should load with as="worker" from same-origin url');
params.set("resource-url",
SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
params.set("as", "worker");
params.set("should-preload", true);
const test_url = "resources/modulepreload-in-early-hints.h2.py?" + params.toString();
window.location.replace(new URL(test_url, window.location));
});

View file

@ -0,0 +1,13 @@
// META: script=/common/utils.js
// META: script=resources/early-hints-helpers.sub.js
// see modulepreload-in-early-hints.h2.window.js for params explanation
test(() => {
const params = new URLSearchParams();
params.set("description", "Modulepreload works in early hints from cross-origin url");
params.set("resource-url",
CROSS_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
params.set("should-preload", true);
const test_url = "resources/modulepreload-in-early-hints.h2.py?" + params.toString();
window.location.replace(new URL(test_url, window.location));
});

View file

@ -1,10 +1,20 @@
// META: script=/common/utils.js
// META: script=resources/early-hints-helpers.sub.js
// params are sent to a Python handler[1] that returns a 103 Early Hints
// response based the values of "resource-url" and "as", and then that response
// is validated by a window test[2] according to the value of "should-preload"
//
// see: https://web-platform-tests.org/writing-tests/h2tests.html
//
// [1]: resources/modulepreload-in-early-hints.h2.py
// [2]: resources/modulepreload-in-early-hints.h2.html
test(() => {
const params = new URLSearchParams();
params.set("description", "Modulepreload works in early hints");
params.set("resource-url",
SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
params.set("should-preload", true);
const test_url = "resources/modulepreload-in-early-hints.h2.py?" + params.toString();
window.location.replace(new URL(test_url, window.location));
});

View file

@ -3,6 +3,11 @@ import os
def handle_headers(frame, request, response):
resource_url = request.GET.first(b"resource-url").decode()
as_value = request.GET.first(b"as", None)
if as_value:
link_header_value = "<{}>; rel=modulepreload; as={}".format(
resource_url, as_value.decode())
else:
link_header_value = "<{}>; rel=modulepreload".format(resource_url)
early_hints = [
(b":status", b"103"),

View file

@ -16,11 +16,14 @@ async function fetchModuleScript(url) {
});
}
promise_test(async (t) => {
const params = new URLSearchParams(window.location.search);
const description = params.get("description");
promise_test(async (t) => {
const resource_url = params.get("resource-url");
const should_preload = params.get("should-preload") === "true";
await fetchModuleScript(resource_url);
assert_true(isPreloadedByEarlyHints(resource_url));
}, "Modulepreload in an early hints.");
assert_equals(isPreloadedByEarlyHints(resource_url), should_preload);
}, description);
</script>
</body>