Bug 1594449 - <link rel="preload"> implemented as a speculative load initiated during the prescan phase in the HTML5 parser, disabled by default, only supports "script" and "styles" types, r=ckerschb

Differential Revision: https://phabricator.services.mozilla.com/D52019

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Honza Bambas 2019-11-27 21:45:12 +00:00
parent 10f4461247
commit ce072f8e6a
15 changed files with 151 additions and 43 deletions

View File

@ -11310,18 +11310,20 @@ NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
void Document::PreloadStyle(nsIURI* uri, const Encoding* aEncoding,
const nsAString& aCrossOriginAttr,
const enum ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity) {
const nsAString& aIntegrity, bool aIsLinkPreload) {
// The CSSLoader will retain this object after we return.
nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
nsCOMPtr<nsIReferrerInfo> referrerInfo =
ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
auto preloadType = aIsLinkPreload ? css::Loader::IsPreload::FromLink
: css::Loader::IsPreload::FromParser;
// Charset names are always ASCII.
Unused << CSSLoader()->LoadSheet(
uri, css::Loader::IsPreload::Yes, NodePrincipal(), aEncoding,
referrerInfo, obs, Element::StringToCORSMode(aCrossOriginAttr),
aIntegrity);
uri, preloadType, NodePrincipal(), aEncoding, referrerInfo, obs,
Element::StringToCORSMode(aCrossOriginAttr), aIntegrity);
}
RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {

View File

@ -3213,7 +3213,7 @@ class Document : public nsINode,
void PreloadStyle(nsIURI* aURI, const Encoding* aEncoding,
const nsAString& aCrossOriginAttr,
ReferrerPolicyEnum aReferrerPolicy,
const nsAString& aIntegrity);
const nsAString& aIntegrity, bool aIsLinkPreload);
/**
* Called by the chrome registry to load style sheets.

View File

@ -13,6 +13,7 @@
#include "mozilla/EventStates.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/HTMLLinkElementBinding.h"
@ -402,7 +403,8 @@ static const DOMTokenListSupportedToken sSupportedRelValues[] = {
nsDOMTokenList* HTMLLinkElement::RelList() {
if (!mRelList) {
if (Preferences::GetBool("network.preload")) {
if (Preferences::GetBool("network.preload") ||
StaticPrefs::network_preload_experimental()) {
mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, sSupportedRelValues);
} else {
mRelList =

View File

@ -156,8 +156,11 @@ inline ModuleLoadRequest* ScriptLoadRequest::AsModuleRequest() {
return static_cast<ModuleLoadRequest*>(this);
}
void ScriptLoadRequest::SetScriptMode(bool aDeferAttr, bool aAsyncAttr) {
if (aAsyncAttr) {
void ScriptLoadRequest::SetScriptMode(bool aDeferAttr, bool aAsyncAttr,
bool aLinkPreload) {
if (aLinkPreload) {
mScriptMode = ScriptMode::eLinkPreload;
} else if (aAsyncAttr) {
mScriptMode = ScriptMode::eAsync;
} else if (aDeferAttr || IsModuleRequest()) {
mScriptMode = ScriptMode::eDeferred;

View File

@ -210,9 +210,19 @@ class ScriptLoadRequest
: ScriptText<Utf8Unit>().clearAndFree();
}
enum class ScriptMode : uint8_t { eBlocking, eDeferred, eAsync };
enum class ScriptMode : uint8_t {
eBlocking,
eDeferred,
eAsync,
eLinkPreload // this is a load initiated by <link rel="preload"
// as="script"> tag
};
void SetScriptMode(bool aDeferAttr, bool aAsyncAttr);
void SetScriptMode(bool aDeferAttr, bool aAsyncAttr, bool aLinkPreload);
bool IsLinkPreloadScript() const {
return mScriptMode == ScriptMode::eLinkPreload;
}
bool IsBlockingScript() const { return mScriptMode == ScriptMode::eBlocking; }

View File

@ -50,6 +50,7 @@
#include "nsICacheInfoChannel.h"
#include "nsITimedChannel.h"
#include "nsIScriptElement.h"
#include "nsISupportsPriority.h"
#include "nsIDocShell.h"
#include "nsContentUtils.h"
#include "nsUnicharUtils.h"
@ -1369,7 +1370,18 @@ nsresult ScriptLoader::StartLoad(ScriptLoadRequest* aRequest) {
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
if (cos) {
if (aRequest->mScriptFromHead && aRequest->IsBlockingScript()) {
if (aRequest->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.
cos->AddClassFlags(nsIClassOfService::Unblocked);
if (nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(channel)) {
sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
}
} else if (aRequest->mScriptFromHead && aRequest->IsBlockingScript()) {
// synchronous head scripts block loading of most other non js/css
// content such as images, Leader implicitely disallows tailing
cos->AddClassFlags(nsIClassOfService::Leader);
@ -1619,7 +1631,7 @@ bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
// It's possible these attributes changed since we started the preload so
// update them here.
request->SetScriptMode(aElement->GetScriptDeferred(),
aElement->GetScriptAsync());
aElement->GetScriptAsync(), false);
AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::Used);
} else {
@ -1646,7 +1658,7 @@ bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
ourCORSMode, sriMetadata, referrerPolicy);
request->mIsInline = false;
request->SetScriptMode(aElement->GetScriptDeferred(),
aElement->GetScriptAsync());
aElement->GetScriptAsync(), false);
// keep request->mScriptFromHead to false so we don't treat non preloaded
// scripts as blockers for full page load. See bug 792438.
@ -1795,7 +1807,7 @@ bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement,
// inline classic scripts ignore both these attributes.
MOZ_ASSERT(!aElement->GetScriptDeferred());
MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync());
request->SetScriptMode(false, aElement->GetScriptAsync());
request->SetScriptMode(false, aElement->GetScriptAsync(), false);
LOG(("ScriptLoadRequest (%p): Created request for inline script",
request.get()));
@ -3724,6 +3736,7 @@ void ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity, bool aScriptFromHead,
bool aAsync, bool aDefer, bool aNoModule,
bool aLinkPreload,
const ReferrerPolicy aReferrerPolicy) {
NS_ENSURE_TRUE_VOID(mDocument);
// Check to see if scripts has been turned off.
@ -3762,7 +3775,7 @@ void ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset,
Element::StringToCORSMode(aCrossOrigin), sriMetadata, aReferrerPolicy);
request->mIsInline = false;
request->mScriptFromHead = aScriptFromHead;
request->SetScriptMode(aDefer, aAsync);
request->SetScriptMode(aDefer, aAsync, aLinkPreload);
request->SetIsPreloadRequest();
if (LOG_ENABLED()) {

View File

@ -322,6 +322,7 @@ class ScriptLoader final : public nsISupports {
const nsAString& aType, const nsAString& aCrossOrigin,
const nsAString& aIntegrity, bool aScriptFromHead,
bool aAsync, bool aDefer, bool aNoModule,
bool aLinkPreload,
const ReferrerPolicy aReferrerPolicy);
/**

View File

@ -1058,9 +1058,9 @@ nsresult Loader::CheckContentPolicy(nsIPrincipal* aLoadingPrincipal,
}
nsContentPolicyType contentPolicyType =
aIsPreload == IsPreload::Yes
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
aIsPreload == IsPreload::No
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
aLoadingPrincipal, aTriggeringPrincipal, aRequestingNode,
@ -1341,9 +1341,9 @@ nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
nsILoadInfo::SEC_ALLOW_CHROME;
nsContentPolicyType contentPolicyType =
aIsPreload == IsPreload::Yes
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
aIsPreload == IsPreload::No
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
// Just load it
nsCOMPtr<nsIChannel> channel;
@ -1482,9 +1482,9 @@ nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
nsContentPolicyType contentPolicyType =
aIsPreload == IsPreload::Yes
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
aIsPreload == IsPreload::No
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
nsCOMPtr<nsIChannel> channel;
// Note we are calling NS_NewChannelWithTriggeringPrincipal here with a node
@ -1528,6 +1528,11 @@ nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
cos->AddClassFlags(nsIClassOfService::Leader);
}
if (aIsPreload == IsPreload::FromLink) {
if (nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(channel)) {
sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
}
}
}
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {

View File

@ -205,7 +205,12 @@ class Loader final {
nsIURI*, SheetParsingMode = eAuthorSheetFeatures,
UseSystemPrincipal = UseSystemPrincipal::No);
enum class IsPreload { No, Yes };
/**
* Yes: this is a speculative load initiated by a <script> tag
* Link: this is a speculative load as well, but initiated by <link
* rel="preload" as="style"> tag
*/
enum class IsPreload { No, FromParser, FromLink };
/**
* Asynchronously load the stylesheet at aURL. If a successful result is

View File

@ -6827,6 +6827,13 @@
value: false
mirror: always
# Whether to use the new experimental preload code based on the speculative loader
# inside the html5 parser.
- name: network.preload-experimental
type: RelaxedAtomicBool
value: false
mirror: always
# Telemetry of traffic categories. Whether or not to enable HttpTrafficAnalyzer.
- name: network.traffic_analyzer.enabled
type: RelaxedAtomicBool

View File

@ -12,6 +12,7 @@ nsHtml5SpeculativeLoad::nsHtml5SpeculativeLoad()
: mOpCode(eSpeculativeLoadUninitialized),
mIsAsync(false),
mIsDefer(false),
mIsLinkPreload(false),
mEncoding(nullptr) {
MOZ_COUNT_CTOR(nsHtml5SpeculativeLoad);
new (&mCharsetOrSrcset) nsString;
@ -61,34 +62,39 @@ void nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor) {
mUrlOrSizes, mCharsetOrSrcset,
mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity,
mCrossOriginOrMedia, mReferrerPolicyOrIntegrity,
mScriptReferrerPolicy, false, mIsAsync, mIsDefer, false);
mScriptReferrerPolicy, false, mIsAsync, mIsDefer, false,
mIsLinkPreload);
break;
case eSpeculativeLoadScriptFromHead:
aExecutor->PreloadScript(
mUrlOrSizes, mCharsetOrSrcset,
mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity,
mCrossOriginOrMedia, mReferrerPolicyOrIntegrity,
mScriptReferrerPolicy, true, mIsAsync, mIsDefer, false);
mScriptReferrerPolicy, true, mIsAsync, mIsDefer, false,
mIsLinkPreload);
break;
case eSpeculativeLoadNoModuleScript:
aExecutor->PreloadScript(
mUrlOrSizes, mCharsetOrSrcset,
mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity,
mCrossOriginOrMedia, mReferrerPolicyOrIntegrity,
mScriptReferrerPolicy, false, mIsAsync, mIsDefer, true);
mScriptReferrerPolicy, false, mIsAsync, mIsDefer, true,
mIsLinkPreload);
break;
case eSpeculativeLoadNoModuleScriptFromHead:
aExecutor->PreloadScript(
mUrlOrSizes, mCharsetOrSrcset,
mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity,
mCrossOriginOrMedia, mReferrerPolicyOrIntegrity,
mScriptReferrerPolicy, true, mIsAsync, mIsDefer, true);
mScriptReferrerPolicy, true, mIsAsync, mIsDefer, true,
mIsLinkPreload);
break;
case eSpeculativeLoadStyle:
aExecutor->PreloadStyle(
mUrlOrSizes, mCharsetOrSrcset, mCrossOriginOrMedia,
mReferrerPolicyOrIntegrity,
mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity);
mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity,
mIsLinkPreload);
break;
case eSpeculativeLoadManifest:
aExecutor->ProcessOfflineManifest(mUrlOrSizes);

View File

@ -125,7 +125,8 @@ class nsHtml5SpeculativeLoad {
nsHtml5String aType, nsHtml5String aCrossOrigin,
nsHtml5String aIntegrity,
nsHtml5String aReferrerPolicy, bool aParserInHead,
bool aAsync, bool aDefer, bool aNoModule) {
bool aAsync, bool aDefer, bool aNoModule,
bool aLinkPreload) {
MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
"Trying to reinitialize a speculative load!");
if (aNoModule) {
@ -152,6 +153,7 @@ class nsHtml5SpeculativeLoad {
mIsAsync = aAsync;
mIsDefer = aDefer;
mIsLinkPreload = aLinkPreload;
}
inline void InitImportStyle(nsString&& aUrl) {
@ -168,8 +170,8 @@ class nsHtml5SpeculativeLoad {
inline void InitStyle(nsHtml5String aUrl, nsHtml5String aCharset,
nsHtml5String aCrossOrigin,
nsHtml5String aReferrerPolicy,
nsHtml5String aIntegrity) {
nsHtml5String aReferrerPolicy, nsHtml5String aIntegrity,
bool aLinkPreload) {
MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
"Trying to reinitialize a speculative load!");
mOpCode = eSpeculativeLoadStyle;
@ -184,6 +186,7 @@ class nsHtml5SpeculativeLoad {
referrerPolicy));
aIntegrity.ToString(
mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity);
mIsLinkPreload = aLinkPreload;
}
/**
@ -261,6 +264,13 @@ class nsHtml5SpeculativeLoad {
bool mIsAsync;
bool mIsDefer;
/**
* 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.
*/
bool mIsLinkPreload;
/* If mOpCode is eSpeculativeLoadPictureSource, this is the value of the
* "sizes" attribute. If the attribute is not set, this will be a void
* string. Otherwise it empty or the value of the url.

View File

@ -7,6 +7,7 @@
#include "nsError.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Likely.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/UniquePtr.h"
nsHtml5TreeBuilder::nsHtml5TreeBuilder(nsHtml5OplessBuilder* aBuilder)
@ -230,7 +231,8 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
aAttributes->contains(nsHtml5AttributeName::ATTR_NOMODULE);
mSpeculativeLoadQueue.AppendElement()->InitScript(
url, charset, type, crossOrigin, integrity, referrerPolicy,
mode == nsHtml5TreeBuilder::IN_HEAD, async, defer, noModule);
mode == nsHtml5TreeBuilder::IN_HEAD, async, defer, noModule,
false);
mCurrentHtmlScriptIsAsyncOrDefer = async || defer;
}
} else if (nsGkAtoms::link == aName) {
@ -252,7 +254,8 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
nsHtml5String referrerPolicy = aAttributes->getValue(
nsHtml5AttributeName::ATTR_REFERRERPOLICY);
mSpeculativeLoadQueue.AppendElement()->InitStyle(
url, charset, crossOrigin, referrerPolicy, integrity);
url, charset, crossOrigin, referrerPolicy, integrity,
false);
}
} else if (rel.LowerCaseEqualsASCII("preconnect")) {
nsHtml5String url =
@ -263,6 +266,43 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
mSpeculativeLoadQueue.AppendElement()->InitPreconnect(
url, crossOrigin);
}
} else if (StaticPrefs::network_preload_experimental() &&
rel.LowerCaseEqualsASCII("preload")) {
nsHtml5String url =
aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
if (url) {
nsHtml5String as =
aAttributes->getValue(nsHtml5AttributeName::ATTR_AS);
nsHtml5String charset =
aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
nsHtml5String crossOrigin = aAttributes->getValue(
nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsHtml5String integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
nsHtml5String referrerPolicy = aAttributes->getValue(
nsHtml5AttributeName::ATTR_REFERRERPOLICY);
// Note that respective speculative loaders for scripts and
// styles check all additional attributes to be equal to use the
// speculative load. So, if any of them is specified and the
// preload has to take the expected effect, those attributes
// must also be specified on the actual tag to use the preload.
// Omitting an attribute on both will make the values equal
// (empty) and thus use the preload.
if (as.LowerCaseEqualsASCII("script")) {
nsHtml5String type =
aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
mSpeculativeLoadQueue.AppendElement()->InitScript(
url, charset, type, crossOrigin, integrity,
referrerPolicy, mode == nsHtml5TreeBuilder::IN_HEAD,
false, false, false, true);
} else if (as.LowerCaseEqualsASCII("style")) {
mSpeculativeLoadQueue.AppendElement()->InitStyle(
url, charset, crossOrigin, referrerPolicy, integrity,
true);
}
// Other "as" values will be supported later.
}
}
}
} else if (nsGkAtoms::video == aName) {
@ -350,7 +390,8 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
nsHtml5AttributeName::ATTR_REFERRERPOLICY);
mSpeculativeLoadQueue.AppendElement()->InitScript(
url, nullptr, type, crossOrigin, integrity, referrerPolicy,
mode == nsHtml5TreeBuilder::IN_HEAD, false, false, false);
mode == nsHtml5TreeBuilder::IN_HEAD, false, false, false,
false);
}
} else if (nsGkAtoms::style == aName) {
mImportScanner.Start();

View File

@ -978,29 +978,31 @@ void nsHtml5TreeOpExecutor::PreloadScript(
const nsAString& aURL, const nsAString& aCharset, const nsAString& aType,
const nsAString& aCrossOrigin, const nsAString& aIntegrity,
dom::ReferrerPolicy aReferrerPolicy, bool aScriptFromHead, bool aAsync,
bool aDefer, bool aNoModule) {
bool aDefer, bool aNoModule, bool aLinkPreload) {
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
mDocument->ScriptLoader()->PreloadURI(
uri, aCharset, aType, aCrossOrigin, aIntegrity, aScriptFromHead, aAsync,
aDefer, aNoModule, GetPreloadReferrerPolicy(aReferrerPolicy));
aDefer, aNoModule, aLinkPreload,
GetPreloadReferrerPolicy(aReferrerPolicy));
}
void nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
const nsAString& aCharset,
const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy,
const nsAString& aIntegrity) {
const nsAString& aIntegrity,
bool aLinkPreload) {
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
mDocument->PreloadStyle(uri, Encoding::ForLabel(aCharset), aCrossOrigin,
GetPreloadReferrerPolicy(aReferrerPolicy),
aIntegrity);
GetPreloadReferrerPolicy(aReferrerPolicy), aIntegrity,
aLinkPreload);
}
void nsHtml5TreeOpExecutor::PreloadImage(

View File

@ -233,12 +233,13 @@ class nsHtml5TreeOpExecutor final
const nsAString& aType, const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
ReferrerPolicy aReferrerPolicy, bool aScriptFromHead,
bool aAsync, bool aDefer, bool aNoModule);
bool aAsync, bool aDefer, bool aNoModule,
bool aLinkPreload);
void PreloadStyle(const nsAString& aURL, const nsAString& aCharset,
const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy,
const nsAString& aIntegrity);
const nsAString& aIntegrity, bool aLinkPreload);
void PreloadImage(const nsAString& aURL, const nsAString& aCrossOrigin,
const nsAString& aSrcset, const nsAString& aSizes,