зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1425310 - Implement modulepreload for link rel. r=yulia,smaug,jonco
Does perform the optional step of fetching descendants and linking: https://html.spec.whatwg.org/multipage/webappapis.html#fetching-scripts:fetch-the-descendants-of-and-link-a-module-script-2 Partially implements some new destinations ("as" attribute values) for modulepreload only (not for preload or link attribute reflection) Differential Revision: https://phabricator.services.mozilla.com/D172368
This commit is contained in:
Родитель
d81ad62bd3
Коммит
aa8c113862
|
@ -311,36 +311,30 @@ void HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
|
|||
aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
|
||||
}
|
||||
|
||||
static const DOMTokenListSupportedToken sSupportedRelValues[] = {
|
||||
// Keep this and the one below in sync with ToLinkMask in
|
||||
// LinkStyle.cpp.
|
||||
// "preload" must come first because it can be disabled.
|
||||
"preload", "prefetch", "dns-prefetch", "stylesheet",
|
||||
"next", "alternate", "preconnect", "icon",
|
||||
"search", "modulepreload", nullptr};
|
||||
// Keep this and the arrays below in sync with ToLinkMask in LinkStyle.cpp.
|
||||
#define SUPPORTED_REL_VALUES_BASE \
|
||||
"prefetch", "dns-prefetch", "stylesheet", "next", "alternate", "preconnect", \
|
||||
"icon", "search", nullptr
|
||||
|
||||
static const DOMTokenListSupportedToken sSupportedRelValuesWithManifest[] = {
|
||||
// Keep this in sync with ToLinkMask in LinkStyle.cpp.
|
||||
// "preload" and "manifest" must come first because they can be disabled.
|
||||
"preload", "manifest", "prefetch", "dns-prefetch", "stylesheet", "next",
|
||||
"alternate", "preconnect", "icon", "search", nullptr};
|
||||
static const DOMTokenListSupportedToken sSupportedRelValueCombinations[][12] = {
|
||||
{SUPPORTED_REL_VALUES_BASE},
|
||||
{"manifest", SUPPORTED_REL_VALUES_BASE},
|
||||
{"preload", SUPPORTED_REL_VALUES_BASE},
|
||||
{"preload", "manifest", SUPPORTED_REL_VALUES_BASE},
|
||||
{"modulepreload", SUPPORTED_REL_VALUES_BASE},
|
||||
{"modulepreload", "manifest", SUPPORTED_REL_VALUES_BASE},
|
||||
{"modulepreload", "preload", SUPPORTED_REL_VALUES_BASE},
|
||||
{"modulepreload", "preload", "manifest", SUPPORTED_REL_VALUES_BASE}};
|
||||
#undef SUPPORTED_REL_VALUES_BASE
|
||||
|
||||
nsDOMTokenList* HTMLLinkElement::RelList() {
|
||||
if (!mRelList) {
|
||||
auto preload = StaticPrefs::network_preload();
|
||||
auto manifest = StaticPrefs::dom_manifest_enabled();
|
||||
if (manifest && preload) {
|
||||
mRelList = new nsDOMTokenList(this, nsGkAtoms::rel,
|
||||
sSupportedRelValuesWithManifest);
|
||||
} else if (manifest && !preload) {
|
||||
mRelList = new nsDOMTokenList(this, nsGkAtoms::rel,
|
||||
&sSupportedRelValuesWithManifest[1]);
|
||||
} else if (!manifest && preload) {
|
||||
mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, sSupportedRelValues);
|
||||
} else { // both false...drop preload
|
||||
mRelList =
|
||||
new nsDOMTokenList(this, nsGkAtoms::rel, &sSupportedRelValues[1]);
|
||||
}
|
||||
int index = (StaticPrefs::dom_manifest_enabled() ? 1 : 0) |
|
||||
(StaticPrefs::network_preload() ? 2 : 0) |
|
||||
(StaticPrefs::network_modulepreload() ? 4 : 0);
|
||||
|
||||
mRelList = new nsDOMTokenList(this, nsGkAtoms::rel,
|
||||
sSupportedRelValueCombinations[index]);
|
||||
}
|
||||
return mRelList;
|
||||
}
|
||||
|
@ -490,7 +484,10 @@ void HTMLLinkElement::
|
|||
}
|
||||
|
||||
if (linkTypes & eMODULE_PRELOAD) {
|
||||
if (!OwnerDoc()->ScriptLoader()->GetModuleLoader()) {
|
||||
ScriptLoader* scriptLoader = OwnerDoc()->ScriptLoader();
|
||||
ModuleLoader* moduleLoader = scriptLoader->GetModuleLoader();
|
||||
|
||||
if (!moduleLoader) {
|
||||
// For the print preview documents, at this moment it doesn't have module
|
||||
// loader yet, as the (print preview) document is not attached to the
|
||||
// nsIContentViewer yet, so it doesn't have the GlobalObject.
|
||||
|
@ -500,9 +497,49 @@ void HTMLLinkElement::
|
|||
return;
|
||||
}
|
||||
|
||||
if (!StaticPrefs::network_modulepreload()) {
|
||||
// Keep behavior from https://phabricator.services.mozilla.com/D149371,
|
||||
// prior to main implementation of modulepreload
|
||||
moduleLoader->DisallowImportMaps();
|
||||
return;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#processing-the-media-attribute
|
||||
// TODO: apply this check for all linkTypes
|
||||
nsAutoString media;
|
||||
if (GetAttr(nsGkAtoms::media, media)) {
|
||||
RefPtr<mozilla::dom::MediaList> mediaList =
|
||||
mozilla::dom::MediaList::Create(NS_ConvertUTF16toUTF8(media));
|
||||
if (!mediaList->Matches(*OwnerDoc())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: per spec, apply this check for ePREFETCH as well
|
||||
if (!HasNonEmptyAttr(nsGkAtoms::href)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoString as;
|
||||
GetAttr(nsGkAtoms::as, as);
|
||||
|
||||
if (!net::IsScriptLikeOrInvalid(as)) {
|
||||
RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
|
||||
this, u"error"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri = GetURI();
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-modulepreload-module-script-graph
|
||||
// Step 1. Disallow further import maps given settings object.
|
||||
OwnerDoc()->ScriptLoader()->GetModuleLoader()->DisallowImportMaps();
|
||||
moduleLoader->DisallowImportMaps();
|
||||
|
||||
StartPreload(nsIContentPolicy::TYPE_SCRIPT);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,9 +62,10 @@ class Element;
|
|||
* the script does not need to be fetched first.
|
||||
* * mIsXSLT
|
||||
* Set if we are in an XSLT request.
|
||||
* * TODO: mIsPreload (will be moved from ScriptFetchOptions)
|
||||
* * mIsPreload
|
||||
* Set for scripts that are preloaded in a
|
||||
* <link rel="preload" as="script"> element.
|
||||
* <link rel="preload" as="script"> or <link rel="modulepreload">
|
||||
* element.
|
||||
*
|
||||
* In addition to describing how the ScriptLoadRequest will be loaded by the
|
||||
* DOM ScriptLoader, the ScriptLoadContext contains fields that facilitate
|
||||
|
@ -113,7 +114,7 @@ class ScriptLoadContext : public JS::loader::LoadContextBase,
|
|||
eDeferred,
|
||||
eAsync,
|
||||
eLinkPreload // this is a load initiated by <link rel="preload"
|
||||
// as="script"> tag
|
||||
// as="script"> or <link rel="modulepreload"> tag
|
||||
};
|
||||
|
||||
void SetScriptMode(bool aDeferAttr, bool aAsyncAttr, bool aLinkPreload);
|
||||
|
|
|
@ -675,12 +675,12 @@ nsresult ScriptLoader::StartLoadInternal(
|
|||
aRequest->GetScriptLoadContext()->IsTracking()));
|
||||
|
||||
if (aRequest->GetScriptLoadContext()->IsLinkPreloadScript()) {
|
||||
// This is <link rel="preload" as="script"> initiated speculative load,
|
||||
// put it to the group that is not blocked by leaders and doesn't block
|
||||
// follower at the same time. Giving it a much higher priority will make
|
||||
// this request be processed ahead of other Unblocked requests, but with
|
||||
// the same weight as Leaders. This will make us behave similar way for
|
||||
// both http2 and http1.
|
||||
// This is <link rel="preload" as="script"> or <link rel="modulepreload">
|
||||
// initiated speculative load, put it to the group that is not blocked by
|
||||
// leaders and doesn't block follower at the same time. Giving it a much
|
||||
// higher priority will make this request be processed ahead of other
|
||||
// Unblocked requests, but with the same weight as Leaders. This will make
|
||||
// us behave similar way for both http2 and http1.
|
||||
ScriptLoadContext::PrioritizeAsPreload(channel);
|
||||
ScriptLoadContext::AddLoadBackgroundFlag(channel);
|
||||
} else if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
|
||||
|
@ -786,7 +786,8 @@ nsresult ScriptLoader::StartLoadInternal(
|
|||
aRequest->mURI, aRequest->CORSMode(), aRequest->mKind);
|
||||
aRequest->GetScriptLoadContext()->NotifyOpen(
|
||||
key, channel, mDocument,
|
||||
aRequest->GetScriptLoadContext()->IsLinkPreloadScript());
|
||||
aRequest->GetScriptLoadContext()->IsLinkPreloadScript(),
|
||||
aRequest->IsModuleRequest());
|
||||
|
||||
if (aEarlyHintPreloaderId) {
|
||||
nsCOMPtr<nsIHttpChannelInternal> channelInternal =
|
||||
|
|
|
@ -11673,12 +11673,21 @@
|
|||
value: false
|
||||
mirror: always
|
||||
|
||||
# Enables `<link rel="preload">` tag and `Link: rel=preload` response header handling.
|
||||
# Enables `<link rel="preload">` tag and `Link: rel=preload` response header
|
||||
# handling.
|
||||
- name: network.preload
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
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.
|
||||
- name: network.modulepreload
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# Enable 103 Early Hint status code (RFC 8297)
|
||||
- name: network.early-hints.enabled
|
||||
type: RelaxedAtomicBool
|
||||
|
|
|
@ -3756,6 +3756,24 @@ nsContentPolicyType AsValueToContentPolicy(const nsAttrValue& aValue) {
|
|||
return nsIContentPolicy::TYPE_INVALID;
|
||||
}
|
||||
|
||||
// TODO: implement this using nsAttrValue's destination enums when support for
|
||||
// the new destinations is added; see this diff for a possible start:
|
||||
// https://phabricator.services.mozilla.com/D172368?vs=705114&id=708720
|
||||
bool IsScriptLikeOrInvalid(const nsAString& aAs) {
|
||||
return !(
|
||||
aAs.LowerCaseEqualsASCII("fetch") || aAs.LowerCaseEqualsASCII("audio") ||
|
||||
aAs.LowerCaseEqualsASCII("document") ||
|
||||
aAs.LowerCaseEqualsASCII("embed") || aAs.LowerCaseEqualsASCII("font") ||
|
||||
aAs.LowerCaseEqualsASCII("frame") || aAs.LowerCaseEqualsASCII("iframe") ||
|
||||
aAs.LowerCaseEqualsASCII("image") ||
|
||||
aAs.LowerCaseEqualsASCII("manifest") ||
|
||||
aAs.LowerCaseEqualsASCII("object") ||
|
||||
aAs.LowerCaseEqualsASCII("report") || aAs.LowerCaseEqualsASCII("style") ||
|
||||
aAs.LowerCaseEqualsASCII("track") || aAs.LowerCaseEqualsASCII("video") ||
|
||||
aAs.LowerCaseEqualsASCII("webidentity") ||
|
||||
aAs.LowerCaseEqualsASCII("xslt"));
|
||||
}
|
||||
|
||||
bool CheckPreloadAttrs(const nsAttrValue& aAs, const nsAString& aType,
|
||||
const nsAString& aMedia,
|
||||
mozilla::dom::Document* aDocument) {
|
||||
|
|
|
@ -1039,6 +1039,7 @@ enum ASDestination : uint8_t {
|
|||
|
||||
void ParseAsValue(const nsAString& aValue, nsAttrValue& aResult);
|
||||
nsContentPolicyType AsValueToContentPolicy(const nsAttrValue& aValue);
|
||||
bool IsScriptLikeOrInvalid(const nsAString& aAs);
|
||||
|
||||
bool CheckPreloadAttrs(const nsAttrValue& aAs, const nsAString& aType,
|
||||
const nsAString& aMedia,
|
||||
|
|
|
@ -334,8 +334,8 @@ class nsHtml5SpeculativeLoad {
|
|||
|
||||
/**
|
||||
* True if and only if this is a speculative load initiated by <link
|
||||
* rel="preload"> tag encounter. Passed to the handling loader as an
|
||||
* indication to raise the priority.
|
||||
* rel="preload"> or <link rel="modulepreload"> tag encounter. Passed to the
|
||||
* handling loader as an indication to raise the priority.
|
||||
*/
|
||||
bool mIsLinkPreload;
|
||||
|
||||
|
@ -387,8 +387,8 @@ class nsHtml5SpeculativeLoad {
|
|||
* value of the "sizes" attribute. If the attribute is not set, this will
|
||||
* be a void string. If mOpCode is eSpeculativeLoadStyle, this
|
||||
* is the value of the "integrity" attribute. If the attribute is not set,
|
||||
* this will be a void string. Otherwise it is empty or the value of the
|
||||
* referrer policy. Otherwise, it is empty or the value of the type attribute.
|
||||
* this will be a void string. Otherwise, it is empty or the value of the type
|
||||
* attribute.
|
||||
*/
|
||||
nsString mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity;
|
||||
/**
|
||||
|
|
|
@ -336,6 +336,35 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
|
|||
}
|
||||
// Other "as" values will be supported later.
|
||||
}
|
||||
} else if (mozilla::StaticPrefs::network_modulepreload() &&
|
||||
rel.LowerCaseEqualsASCII("modulepreload")) {
|
||||
nsHtml5String url =
|
||||
aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
|
||||
if (url && url.Length() != 0) {
|
||||
nsHtml5String as =
|
||||
aAttributes->getValue(nsHtml5AttributeName::ATTR_AS);
|
||||
nsAutoString asString;
|
||||
as.ToString(asString);
|
||||
if (net::IsScriptLikeOrInvalid(asString)) {
|
||||
nsHtml5String charset =
|
||||
aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
|
||||
RefPtr<nsAtom> moduleType = nsGkAtoms::_module;
|
||||
nsHtml5String type =
|
||||
nsHtml5String::FromAtom(moduleType.forget());
|
||||
nsHtml5String crossOrigin = aAttributes->getValue(
|
||||
nsHtml5AttributeName::ATTR_CROSSORIGIN);
|
||||
nsHtml5String media =
|
||||
aAttributes->getValue(nsHtml5AttributeName::ATTR_MEDIA);
|
||||
nsHtml5String integrity = aAttributes->getValue(
|
||||
nsHtml5AttributeName::ATTR_INTEGRITY);
|
||||
nsHtml5String referrerPolicy = aAttributes->getValue(
|
||||
nsHtml5AttributeName::ATTR_REFERRERPOLICY);
|
||||
mSpeculativeLoadQueue.AppendElement()->InitScript(
|
||||
url, charset, type, crossOrigin, media, integrity,
|
||||
referrerPolicy, mode == nsHtml5TreeBuilder::IN_HEAD,
|
||||
false, false, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (nsGkAtoms::video == aName) {
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[avoid-delaying-onload-link-modulepreload-exec.html]
|
||||
expected: ERROR
|
|
@ -1,2 +0,0 @@
|
|||
[avoid-delaying-onload-link-modulepreload.html]
|
||||
expected: ERROR
|
|
@ -1,47 +0,0 @@
|
|||
[modulepreload.html]
|
||||
expected: TIMEOUT
|
||||
[link rel=modulepreload]
|
||||
expected: TIMEOUT
|
||||
|
||||
[link rel=modulepreload with submodules]
|
||||
expected: NOTRUN
|
||||
|
||||
[link rel=modulepreload for a module with syntax error]
|
||||
expected: NOTRUN
|
||||
|
||||
[link rel=modulepreload for a module with network error]
|
||||
expected: NOTRUN
|
||||
|
||||
[link rel=modulepreload with bad href attribute]
|
||||
expected: NOTRUN
|
||||
|
||||
[link rel=modulepreload as=script]
|
||||
expected: NOTRUN
|
||||
|
||||
[link rel=modulepreload with invalid as= value]
|
||||
expected: NOTRUN
|
||||
|
||||
[link rel=modulepreload with integrity match]
|
||||
expected: NOTRUN
|
||||
|
||||
[link rel=modulepreload with integrity mismatch]
|
||||
expected: NOTRUN
|
||||
|
||||
[cross-origin link rel=modulepreload]
|
||||
expected: NOTRUN
|
||||
|
||||
[cross-origin link rel=modulepreload crossorigin=anonymous]
|
||||
expected: NOTRUN
|
||||
|
||||
[same-origin link rel=modulepreload crossorigin=anonymous]
|
||||
expected: NOTRUN
|
||||
|
||||
[same-origin link rel=modulepreload crossorigin=use-credentials]
|
||||
expected: NOTRUN
|
||||
|
||||
[cross-origin link rel=modulepreload crossorigin=use-credentials]
|
||||
expected: NOTRUN
|
||||
|
||||
[link rel=modulepreload with integrity match2]
|
||||
expected: NOTRUN
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<link rel="modulepreload" href="resources/module1.js?empty-string" as="" data-as="">
|
||||
<link rel="modulepreload" href="resources/module1.js?audio" as="audio" data-as="audio">
|
||||
<link rel="modulepreload" href="resources/module1.js?audioworklet" as="audioworklet" data-as="audioworklet">
|
||||
<link rel="modulepreload" href="resources/module1.js?document" as="document" data-as="document">
|
||||
<link rel="modulepreload" href="resources/module1.js?embed" as="embed" data-as="embed">
|
||||
<link rel="modulepreload" href="resources/module1.js?font" as="font" data-as="font">
|
||||
<link rel="modulepreload" href="resources/module1.js?frame" as="frame" data-as="frame">
|
||||
<link rel="modulepreload" href="resources/module1.js?iframe" as="iframe" data-as="iframe">
|
||||
<link rel="modulepreload" href="resources/module1.js?image" as="image" data-as="image">
|
||||
<link rel="modulepreload" href="resources/module1.js?manifest" as="manifest" data-as="manifest">
|
||||
<link rel="modulepreload" href="resources/module1.js?object" as="object" data-as="object">
|
||||
<link rel="modulepreload" href="resources/module1.js?paintworklet" as="paintworklet" data-as="paintworklet">
|
||||
<link rel="modulepreload" href="resources/module1.js?report" as="report" data-as="report">
|
||||
<link rel="modulepreload" href="resources/module1.js?script" as="script" data-as="script">
|
||||
<link rel="modulepreload" href="resources/module1.js?serviceworker" as="serviceworker" data-as="serviceworker">
|
||||
<link rel="modulepreload" href="resources/module1.js?sharedworker" as="sharedworker" data-as="sharedworker">
|
||||
<link rel="modulepreload" href="resources/module1.js?style" as="style" data-as="style">
|
||||
<link rel="modulepreload" href="resources/module1.js?track" as="track" data-as="track">
|
||||
<link rel="modulepreload" href="resources/module1.js?video" as="video" data-as="video">
|
||||
<link rel="modulepreload" href="resources/module1.js?webidentity" as="webidentity" data-as="webidentity">
|
||||
<link rel="modulepreload" href="resources/module1.js?worker" as="worker" data-as="worker">
|
||||
<link rel="modulepreload" href="resources/module1.js?xslt" as="xslt" data-as="xslt">
|
||||
<link rel="modulepreload" href="resources/module1.js?fetch" as="fetch" data-as="fetch">
|
||||
<link rel="modulepreload" href="resources/module1.js?invalid-dest" as="invalid-dest" data-as="invalid-dest">
|
||||
<link rel="modulepreload" href="resources/module1.js?iMaGe" as="iMaGe" data-as="iMaGe">
|
||||
<link rel="modulepreload" href="resources/module1.js?sCrIpT" as="sCrIpT" data-as="sCrIpT">
|
||||
<body>
|
||||
<script>
|
||||
// compared to modulepreload.html, this tests behavior when elements are
|
||||
// initially on an HTML page instead of being added by JS
|
||||
|
||||
const scriptLikes = [
|
||||
'audioworklet',
|
||||
'paintworklet',
|
||||
'script',
|
||||
'serviceworker',
|
||||
'sharedworker',
|
||||
'worker',
|
||||
];
|
||||
|
||||
const goodAsValues = ['', 'invalid-dest', 'sCrIpT', ...scriptLikes];
|
||||
|
||||
for (const link of document.querySelectorAll('link')) {
|
||||
const asValue = link.dataset.as; // don't depend on "as" attribute reflection
|
||||
const good = goodAsValues.includes(asValue);
|
||||
|
||||
// promise tests are queued sequentially, so create the promise here to
|
||||
// ensure we don't miss the error event
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
link.onload = good ? resolve : reject;
|
||||
link.onerror = good ? reject : resolve;
|
||||
});
|
||||
|
||||
promise_test(() => promise.then(() => {
|
||||
const downloads = performance
|
||||
.getEntriesByName(new URL(link.href, location.href))
|
||||
.filter(entry => entry.transferSize > 0)
|
||||
.length;
|
||||
assert_equals(downloads, good ? 1 : 0);
|
||||
|
||||
}), `Modulepreload with as="${asValue}"`);
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,18 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<link rel="modulepreload" href="resources/module1.js" integrity="sha384-invalid">
|
||||
<script type="module" src="resources/module1.js" id="myscript"></script>
|
||||
<body>
|
||||
<script>
|
||||
// compared to modulepreload.html, this tests behavior when elements are
|
||||
// initially on an HTML page instead of being added by JS
|
||||
promise_test(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let myscript = document.querySelector('#myscript');
|
||||
myscript.onerror = resolve;
|
||||
myscript.onload = reject;
|
||||
});
|
||||
}, "Script should not be loaded if modulepreload's integrity is invalid");
|
||||
</script>
|
|
@ -33,6 +33,15 @@ function attachAndWaitForError(element) {
|
|||
});
|
||||
}
|
||||
|
||||
function attachAndWaitForTimeout(element, t) {
|
||||
return new Promise((resolve, reject) => {
|
||||
element.onload = reject;
|
||||
element.onerror = reject;
|
||||
t.step_timeout(resolve, 1000);
|
||||
document.body.appendChild(element);
|
||||
});
|
||||
}
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
|
@ -214,13 +223,21 @@ promise_test(function(t) {
|
|||
link.href = 'resources/module1.js?as-image';
|
||||
link.as = 'image'
|
||||
return attachAndWaitForError(link);
|
||||
}, 'link rel=modulepreload with invalid as= value');
|
||||
}, 'link rel=modulepreload with non-script-like as= value (image)');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = 'resources/module1.js?as-xslt';
|
||||
link.as = 'xslt'
|
||||
return attachAndWaitForError(link);
|
||||
}, 'link rel=modulepreload with non-script-like as= value (xslt)');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = 'resources/module1.js?integrity-match';
|
||||
link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc=%'
|
||||
link.integrity = 'sha256-+Ks3iNIiTq2ujlWhvB056cmXobrCFpU9hd60xZ1WCaA='
|
||||
return attachAndWaitForLoad(link);
|
||||
}, 'link rel=modulepreload with integrity match');
|
||||
|
||||
|
@ -228,7 +245,7 @@ promise_test(function(t) {
|
|||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = 'resources/module1.mjs?integrity-match';
|
||||
link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc=%'
|
||||
link.integrity = 'sha256-+Ks3iNIiTq2ujlWhvB056cmXobrCFpU9hd60xZ1WCaA='
|
||||
return attachAndWaitForLoad(link);
|
||||
}, 'link rel=modulepreload with integrity match2');
|
||||
|
||||
|
@ -240,5 +257,88 @@ promise_test(function(t) {
|
|||
return attachAndWaitForError(link);
|
||||
}, 'link rel=modulepreload with integrity mismatch');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = 'resources/module1.mjs?integrity-doesnotmatch';
|
||||
link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc='
|
||||
return attachAndWaitForError(link);
|
||||
}, 'link rel=modulepreload with integrity mismatch2');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = 'resources/module1.mjs?integrity-invalid';
|
||||
link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc=%'
|
||||
return attachAndWaitForError(link);
|
||||
}, 'link rel=modulepreload with integrity mismatch3');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link1 = document.createElement('link');
|
||||
var link2 = document.createElement('link');
|
||||
link1.rel = 'modulepreload';
|
||||
link2.rel = 'modulepreload';
|
||||
link1.href = 'resources/module1.js?same-url';
|
||||
link2.href = 'resources/module1.js?same-url';
|
||||
return Promise.all([
|
||||
attachAndWaitForLoad(link1),
|
||||
attachAndWaitForLoad(link2),
|
||||
]);
|
||||
}, 'multiple link rel=modulepreload with same href');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link1 = document.createElement('link');
|
||||
var link2 = document.createElement('link');
|
||||
link1.rel = 'modulepreload';
|
||||
link2.rel = 'modulepreload';
|
||||
link1.href = 'resources/module2.js?child-before';
|
||||
link2.href = 'resources/module1.js?child-before';
|
||||
return attachAndWaitForLoad(link1)
|
||||
.then(() => attachAndWaitForLoad(link2))
|
||||
.then(() => new Promise(r => t.step_timeout(r, 1000)))
|
||||
.then(() => {
|
||||
verifyNumberOfDownloads('resources/module2.js?child-before', 1);
|
||||
});
|
||||
|
||||
}, 'multiple link rel=modulepreload with child module before parent');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = 'resources/module1.mjs?matching-media';
|
||||
link.media = 'all';
|
||||
return attachAndWaitForLoad(link);
|
||||
}, 'link rel=modulepreload with matching media');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = 'resources/module1.mjs?non-matching-media';
|
||||
link.media = 'not all';
|
||||
return attachAndWaitForTimeout(link, t);
|
||||
}, 'link rel=modulepreload with non-matching media');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = 'resources/module1.mjs?empty-media';
|
||||
link.media = '';
|
||||
return attachAndWaitForLoad(link);
|
||||
}, 'link rel=modulepreload with empty media');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = '';
|
||||
return attachAndWaitForTimeout(link, t);
|
||||
}, 'link rel=modulepreload with empty href');
|
||||
|
||||
promise_test(function(t) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'modulepreload';
|
||||
link.href = '';
|
||||
link.as = 'fetch';
|
||||
return attachAndWaitForTimeout(link, t);
|
||||
}, 'link rel=modulepreload with empty href and invalid as= value');
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -72,11 +72,10 @@ already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
nsAutoString as, charset, crossOrigin, integrity, referrerPolicy, srcset,
|
||||
nsAutoString as, charset, crossOrigin, integrity, referrerPolicy, rel, srcset,
|
||||
sizes, type, url;
|
||||
|
||||
nsCOMPtr<nsIURI> uri = aLinkElement->GetURI();
|
||||
aLinkElement->GetAs(as);
|
||||
aLinkElement->GetCharset(charset);
|
||||
aLinkElement->GetImageSrcset(srcset);
|
||||
aLinkElement->GetImageSizes(sizes);
|
||||
|
@ -84,7 +83,15 @@ already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
|
|||
aLinkElement->GetCrossOrigin(crossOrigin);
|
||||
aLinkElement->GetIntegrity(integrity);
|
||||
aLinkElement->GetReferrerPolicy(referrerPolicy);
|
||||
aLinkElement->GetType(type);
|
||||
aLinkElement->GetRel(rel);
|
||||
|
||||
if (rel.LowerCaseEqualsASCII("modulepreload")) {
|
||||
as = u"script"_ns;
|
||||
type = u"module"_ns;
|
||||
} else {
|
||||
aLinkElement->GetAs(as);
|
||||
aLinkElement->GetType(type);
|
||||
}
|
||||
|
||||
auto result = PreloadOrCoalesce(uri, url, aPolicyType, as, type, charset,
|
||||
srcset, sizes, integrity, crossOrigin,
|
||||
|
|
|
@ -124,7 +124,8 @@ void PreloaderBase::AddLoadBackgroundFlag(nsIChannel* aChannel) {
|
|||
}
|
||||
|
||||
void PreloaderBase::NotifyOpen(const PreloadHashKey& aKey,
|
||||
dom::Document* aDocument, bool aIsPreload) {
|
||||
dom::Document* aDocument, bool aIsPreload,
|
||||
bool aIsModule) {
|
||||
if (aDocument) {
|
||||
DebugOnly<bool> alreadyRegistered =
|
||||
aDocument->Preloads().RegisterPreload(aKey, this);
|
||||
|
@ -137,7 +138,10 @@ void PreloaderBase::NotifyOpen(const PreloadHashKey& aKey,
|
|||
mKey = aKey;
|
||||
mIsUsed = !aIsPreload;
|
||||
|
||||
if (!mIsUsed && !mUsageTimer) {
|
||||
// Start usage timer for rel="preload", but not for rel="modulepreload"
|
||||
// because modules may be loaded for functionality the user does not
|
||||
// immediately interact with after page load (e.g. a docs search box)
|
||||
if (!aIsModule && !mIsUsed && !mUsageTimer) {
|
||||
auto callback = MakeRefPtr<UsageTimer>(this, aDocument);
|
||||
NS_NewTimerWithCallback(getter_AddRefs(mUsageTimer), callback, 10000,
|
||||
nsITimer::TYPE_ONE_SHOT);
|
||||
|
@ -147,8 +151,9 @@ void PreloaderBase::NotifyOpen(const PreloadHashKey& aKey,
|
|||
}
|
||||
|
||||
void PreloaderBase::NotifyOpen(const PreloadHashKey& aKey, nsIChannel* aChannel,
|
||||
dom::Document* aDocument, bool aIsPreload) {
|
||||
NotifyOpen(aKey, aDocument, aIsPreload);
|
||||
dom::Document* aDocument, bool aIsPreload,
|
||||
bool aIsModule) {
|
||||
NotifyOpen(aKey, aDocument, aIsPreload, aIsModule);
|
||||
mChannel = aChannel;
|
||||
|
||||
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
||||
|
|
|
@ -45,9 +45,10 @@ class PreloaderBase : public SupportsWeakPtr, public nsISupports {
|
|||
// preload service to provide coalescing, and access to the preload when it
|
||||
// should be used for an actual load.
|
||||
void NotifyOpen(const PreloadHashKey& aKey, dom::Document* aDocument,
|
||||
bool aIsPreload);
|
||||
bool aIsPreload, bool aIsModule = false);
|
||||
void NotifyOpen(const PreloadHashKey& aKey, nsIChannel* aChannel,
|
||||
dom::Document* aDocument, bool aIsPreload);
|
||||
dom::Document* aDocument, bool aIsPreload,
|
||||
bool aIsModule = false);
|
||||
|
||||
// Called when the load is about to be started all over again and thus this
|
||||
// PreloaderBase will be registered again with the same key. This method
|
||||
|
|
|
@ -679,6 +679,7 @@ STATIC_ATOMS = [
|
|||
Atom("mixed", "mixed"),
|
||||
Atom("messagemanagergroup", "messagemanagergroup"),
|
||||
Atom("mod", "mod"),
|
||||
Atom("_module", "module"),
|
||||
Atom("mode", "mode"),
|
||||
Atom("modifiers", "modifiers"),
|
||||
Atom("monochrome", "monochrome"),
|
||||
|
|
Загрузка…
Ссылка в новой задаче