Bug 1618536 - Preload for fonts, r=heycam,hsivonen

Depends on D72118

Differential Revision: https://phabricator.services.mozilla.com/D70235
This commit is contained in:
Honza Bambas 2020-05-11 14:14:32 +00:00
parent ef0d4e6dd1
commit ec52ba8274
14 changed files with 293 additions and 73 deletions

View File

@ -9,6 +9,7 @@
#include "gfxFontConstants.h"
#include "gfxFontSrcPrincipal.h"
#include "gfxFontSrcURI.h"
#include "FontPreloader.h"
#include "mozilla/css/Loader.h"
#include "mozilla/dom/CSSFontFaceRule.h"
#include "mozilla/dom/DocumentInlines.h"
@ -37,7 +38,6 @@
#include "nsContentUtils.h"
#include "nsDeviceContext.h"
#include "nsFontFaceLoader.h"
#include "nsIClassOfService.h"
#include "nsIConsoleService.h"
#include "nsIContentPolicy.h"
#include "nsIDocShell.h"
@ -45,7 +45,6 @@
#include "nsILoadContext.h"
#include "nsINetworkPredictor.h"
#include "nsIPrincipal.h"
#include "nsISupportsPriority.h"
#include "nsIWebNavigation.h"
#include "nsNetUtil.h"
#include "nsIInputStream.h"
@ -573,87 +572,70 @@ nsresult FontFaceSet::StartLoad(gfxUserFontEntry* aUserFontEntry,
nsresult rv;
nsCOMPtr<nsIStreamLoader> streamLoader;
nsCOMPtr<nsILoadGroup> loadGroup(mDocument->GetDocumentLoadGroup());
gfxFontSrcPrincipal* principal = aUserFontEntry->GetPrincipal();
RefPtr<nsFontFaceLoader> fontLoader;
uint32_t securityFlags = 0;
if (aFontFaceSrc->mURI->get()->SchemeIs("file")) {
securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
} else {
securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
}
auto preloadKey = PreloadHashKey::CreateAsFont(
aFontFaceSrc->mURI->get(), CORS_ANONYMOUS,
aFontFaceSrc->mReferrerInfo->ReferrerPolicy());
RefPtr<PreloaderBase> preload =
mDocument->Preloads().LookupPreload(&preloadKey);
nsCOMPtr<nsIChannel> channel;
// Note we are calling NS_NewChannelWithTriggeringPrincipal() with both a
// node and a principal. This is because the document where the font is
// being loaded might have a different origin from the principal of the
// stylesheet that initiated the font load.
rv = NS_NewChannelWithTriggeringPrincipal(
getter_AddRefs(channel), aFontFaceSrc->mURI->get(), mDocument,
principal ? principal->get() : nullptr, securityFlags,
nsIContentPolicy::TYPE_FONT,
nullptr, // PerformanceStorage
loadGroup);
NS_ENSURE_SUCCESS(rv, rv);
if (preload) {
fontLoader = new nsFontFaceLoader(aUserFontEntry, aFontFaceSrc->mURI->get(),
this, preload->Channel());
RefPtr<nsFontFaceLoader> fontLoader = new nsFontFaceLoader(
aUserFontEntry, aFontFaceSrc->mURI->get(), this, channel);
mLoaders.PutEntry(fontLoader);
if (LOG_ENABLED()) {
nsCOMPtr<nsIURI> referrer =
aFontFaceSrc->mReferrerInfo
? aFontFaceSrc->mReferrerInfo->GetOriginalReferrer()
: nullptr;
LOG(
("userfonts (%p) download start - font uri: (%s) "
"referrer uri: (%s)\n",
fontLoader.get(), aFontFaceSrc->mURI->GetSpecOrDefault().get(),
referrer ? referrer->GetSpecOrDefault().get() : ""));
}
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
if (httpChannel) {
rv = httpChannel->SetReferrerInfo(aFontFaceSrc->mReferrerInfo);
Unused << NS_WARN_IF(NS_FAILED(rv));
rv = httpChannel->SetRequestHeader(
NS_LITERAL_CSTRING("Accept"),
NS_LITERAL_CSTRING("application/font-woff2;q=1.0,application/"
"font-woff;q=0.9,*/*;q=0.8"),
false);
rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader,
fontLoader);
NS_ENSURE_SUCCESS(rv, rv);
// For WOFF and WOFF2, we should tell servers/proxies/etc NOT to try
// and apply additional compression at the content-encoding layer
if (aFontFaceSrc->mFormatFlags & (gfxUserFontSet::FLAG_FORMAT_WOFF |
gfxUserFontSet::FLAG_FORMAT_WOFF2)) {
rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
NS_LITERAL_CSTRING("identity"), false);
NS_ENSURE_SUCCESS(rv, rv);
rv = preload->AsyncConsume(streamLoader);
// We don't want this to hang around regardless of the result, there will be
// no coalescing of later found <link preload> tags for fonts.
mDocument->Preloads().DeregisterPreload(&preloadKey);
} else {
// No preload found, open a channel.
rv = NS_ERROR_FAILURE;
}
nsCOMPtr<nsILoadGroup> loadGroup(mDocument->GetDocumentLoadGroup());
if (NS_FAILED(rv)) {
nsCOMPtr<nsIChannel> channel;
rv = FontPreloader::BuildChannel(
getter_AddRefs(channel), aFontFaceSrc->mURI->get(), CORS_ANONYMOUS,
dom::ReferrerPolicy::_empty /* not used */, aUserFontEntry,
aFontFaceSrc, mDocument, loadGroup, nullptr, false);
NS_ENSURE_SUCCESS(rv, rv);
fontLoader = new nsFontFaceLoader(aUserFontEntry, aFontFaceSrc->mURI->get(),
this, channel);
if (LOG_ENABLED()) {
nsCOMPtr<nsIURI> referrer =
aFontFaceSrc->mReferrerInfo
? aFontFaceSrc->mReferrerInfo->GetOriginalReferrer()
: nullptr;
LOG((
"userfonts (%p) download start - font uri: (%s) referrer uri: (%s)\n",
fontLoader.get(), aFontFaceSrc->mURI->GetSpecOrDefault().get(),
referrer ? referrer->GetSpecOrDefault().get() : ""));
}
rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader,
fontLoader);
NS_ENSURE_SUCCESS(rv, rv);
rv = channel->AsyncOpen(streamLoader);
if (NS_FAILED(rv)) {
fontLoader->DropChannel(); // explicitly need to break ref cycle
}
}
nsCOMPtr<nsISupportsPriority> priorityChannel(do_QueryInterface(channel));
if (priorityChannel) {
priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGH);
}
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
if (cos) {
cos->AddClassFlags(nsIClassOfService::TailForbidden);
}
rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader, fontLoader);
NS_ENSURE_SUCCESS(rv, rv);
mLoaders.PutEntry(fontLoader);
net::PredictorLearn(aFontFaceSrc->mURI->get(), mDocument->GetDocumentURI(),
nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, loadGroup);
rv = channel->AsyncOpen(streamLoader);
if (NS_FAILED(rv)) {
fontLoader->DropChannel(); // explicitly need to break ref cycle
}
if (NS_SUCCEEDED(rv)) {
fontLoader->StartedLoading(streamLoader);
// let the font entry remember the loader, in case we need to cancel it

View File

@ -0,0 +1,120 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FontPreloader.h"
#include "gfxUserFontSet.h"
#include "nsIClassOfService.h"
#include "nsISupportsPriority.h"
namespace mozilla {
FontPreloader::FontPreloader()
: FetchPreloader(nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD) {}
void FontPreloader::PrioritizeAsPreload() { PrioritizeAsPreload(Channel()); }
nsresult FontPreloader::CreateChannel(
nsIChannel** aChannel, nsIURI* aURI, const CORSMode aCORSMode,
const dom::ReferrerPolicy& aReferrerPolicy, dom::Document* aDocument,
nsILoadGroup* aLoadGroup, nsIInterfaceRequestor* aCallbacks) {
return BuildChannel(aChannel, aURI, aCORSMode, aReferrerPolicy, nullptr,
nullptr, aDocument, aLoadGroup, aCallbacks, true);
}
// static
void FontPreloader::PrioritizeAsPreload(nsIChannel* aChannel) {
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(aChannel));
if (cos) {
cos->AddClassFlags(nsIClassOfService::Unblocked);
}
}
// static
nsresult FontPreloader::BuildChannel(
nsIChannel** aChannel, nsIURI* aURI, const CORSMode aCORSMode,
const dom::ReferrerPolicy& aReferrerPolicy,
gfxUserFontEntry* aUserFontEntry, const gfxFontFaceSrc* aFontFaceSrc,
dom::Document* aDocument, nsILoadGroup* aLoadGroup,
nsIInterfaceRequestor* aCallbacks, bool aIsPreload) {
nsresult rv;
nsIPrincipal* principal = aUserFontEntry
? (aUserFontEntry->GetPrincipal()
? aUserFontEntry->GetPrincipal()->get()
: nullptr)
: aDocument->NodePrincipal();
// aCORSMode is ignored. We always load as crossorigin=anonymous, but a
// preload started with anything other then "anonymous" will never be found.
uint32_t securityFlags = 0;
if (aURI->SchemeIs("file")) {
securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
} else {
securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
}
nsContentPolicyType contentPolicyType =
aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD
: nsIContentPolicy::TYPE_FONT;
nsCOMPtr<nsIChannel> channel;
// Note we are calling NS_NewChannelWithTriggeringPrincipal() with both a
// node and a principal. This is because the document where the font is
// being loaded might have a different origin from the principal of the
// stylesheet that initiated the font load.
rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel), aURI,
aDocument, principal, securityFlags,
contentPolicyType,
nullptr, // PerformanceStorage
aLoadGroup);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
if (httpChannel) {
rv = httpChannel->SetRequestHeader(
NS_LITERAL_CSTRING("Accept"),
NS_LITERAL_CSTRING("application/font-woff2;q=1.0,application/"
"font-woff;q=0.9,*/*;q=0.8"),
false);
NS_ENSURE_SUCCESS(rv, rv);
if (aFontFaceSrc) {
rv = httpChannel->SetReferrerInfo(aFontFaceSrc->mReferrerInfo);
Unused << NS_WARN_IF(NS_FAILED(rv));
// For WOFF and WOFF2, we should tell servers/proxies/etc NOT to try
// and apply additional compression at the content-encoding layer
if (aFontFaceSrc->mFormatFlags & (gfxUserFontSet::FLAG_FORMAT_WOFF |
gfxUserFontSet::FLAG_FORMAT_WOFF2)) {
rv = httpChannel->SetRequestHeader(
NS_LITERAL_CSTRING("Accept-Encoding"),
NS_LITERAL_CSTRING("identity"), false);
NS_ENSURE_SUCCESS(rv, rv);
}
} else {
nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(
aDocument->GetDocumentURIAsReferrer(), aReferrerPolicy);
rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
nsCOMPtr<nsISupportsPriority> priorityChannel(do_QueryInterface(channel));
if (priorityChannel) {
priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGH);
}
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
if (cos) {
cos->AddClassFlags(nsIClassOfService::TailForbidden);
}
channel.forget(aChannel);
return NS_OK;
}
} // namespace mozilla

View File

@ -0,0 +1,42 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef FontPreloader_h_
#define FontPreloader_h_
#include "mozilla/FetchPreloader.h"
class gfxUserFontEntry;
struct gfxFontFaceSrc;
namespace mozilla {
class FontPreloader final : public FetchPreloader {
public:
FontPreloader();
// PreloaderBase
static void PrioritizeAsPreload(nsIChannel* aChannel);
void PrioritizeAsPreload() override;
static nsresult BuildChannel(
nsIChannel** aChannel, nsIURI* aURI, const CORSMode aCORSMode,
const dom::ReferrerPolicy& aReferrerPolicy,
gfxUserFontEntry* aUserFontEntry, const gfxFontFaceSrc* aFontFaceSrc,
dom::Document* aDocument, nsILoadGroup* aLoadGroup,
nsIInterfaceRequestor* aCallbacks, bool aIsPreload);
protected:
nsresult CreateChannel(nsIChannel** aChannel, nsIURI* aURI,
const CORSMode aCORSMode,
const dom::ReferrerPolicy& aReferrerPolicy,
dom::Document* aDocument, nsILoadGroup* aLoadGroup,
nsIInterfaceRequestor* aCallbacks) override;
};
} // namespace mozilla
#endif

View File

@ -10,6 +10,7 @@
#include "mozilla/ScopeExit.h"
#include "nsIChannel.h"
#include "nsIInputStream.h"
#include "nsISupportsPriority.h"
#include <limits>

View File

@ -77,6 +77,7 @@ EXPORTS.mozilla += [
'CSSPropFlags.h',
'DeclarationBlock.h',
'DocumentStyleRootIterator.h',
'FontPreloader.h',
'GeckoBindings.h',
'GlobalStyleSheetCache.h',
'ImportScanner.h',
@ -178,6 +179,7 @@ UNIFIED_SOURCES += [
'FontFace.cpp',
'FontFaceSet.cpp',
'FontFaceSetIterator.cpp',
'FontPreloader.cpp',
'GeckoBindings.cpp',
'GlobalStyleSheetCache.cpp',
'GroupRule.cpp',

View File

@ -121,6 +121,10 @@ void nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor) {
case eSpeculativeLoadPreconnect:
aExecutor->Preconnect(mUrlOrSizes, mCrossOriginOrMedia);
break;
case eSpeculativeLoadFont:
aExecutor->PreloadFont(mUrlOrSizes, mCrossOriginOrMedia,
mReferrerPolicyOrIntegrity);
break;
case eSpeculativeLoadFetch:
aExecutor->PreloadFetch(mUrlOrSizes, mCrossOriginOrMedia,
mReferrerPolicyOrIntegrity);

View File

@ -31,6 +31,7 @@ enum eHtml5SpeculativeLoad {
eSpeculativeLoadSetDocumentCharset,
eSpeculativeLoadSetDocumentMode,
eSpeculativeLoadPreconnect,
eSpeculativeLoadFont,
eSpeculativeLoadFetch
};
@ -92,6 +93,23 @@ class nsHtml5SpeculativeLoad {
mIsLinkPreload = aLinkPreload;
}
inline void InitFont(nsHtml5String aUrl, nsHtml5String aCrossOrigin,
nsHtml5String aReferrerPolicy) {
MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
"Trying to reinitialize a speculative load!");
mOpCode = eSpeculativeLoadFont;
aUrl.ToString(mUrlOrSizes);
aCrossOrigin.ToString(mCrossOriginOrMedia);
nsString
referrerPolicy; // Not Auto, because using it to hold nsStringBuffer*
aReferrerPolicy.ToString(referrerPolicy);
mReferrerPolicyOrIntegrity.Assign(
nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
referrerPolicy));
// This can be only triggered by <link rel=preload type=font>
mIsLinkPreload = true;
}
inline void InitFetch(nsHtml5String aUrl, nsHtml5String aCrossOrigin,
nsHtml5String aReferrerPolicy) {
MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,

View File

@ -313,6 +313,9 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
nsHtml5AttributeName::ATTR_IMAGESIZES);
mSpeculativeLoadQueue.AppendElement()->InitImage(
url, crossOrigin, referrerPolicy, srcset, sizes, true);
} else if (as.LowerCaseEqualsASCII("font")) {
mSpeculativeLoadQueue.AppendElement()->InitFont(
url, crossOrigin, referrerPolicy);
} else if (as.LowerCaseEqualsASCII("fetch")) {
mSpeculativeLoadQueue.AppendElement()->InitFetch(
url, crossOrigin, referrerPolicy);

View File

@ -1031,6 +1031,17 @@ void nsHtml5TreeOpExecutor::PreloadPictureSource(const nsAString& aSrcset,
mDocument->PreloadPictureImageSource(aSrcset, aSizes, aType, aMedia);
}
void nsHtml5TreeOpExecutor::PreloadFont(const nsAString& aURL,
const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy) {
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy);
}
void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL,
const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy) {

View File

@ -252,6 +252,9 @@ class nsHtml5TreeOpExecutor final
void PreloadPictureSource(const nsAString& aSrcset, const nsAString& aSizes,
const nsAString& aType, const nsAString& aMedia);
void PreloadFont(const nsAString& aURL, const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy);
void PreloadFetch(const nsAString& aURL, const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy);

View File

@ -154,6 +154,16 @@ PreloadHashKey PreloadHashKey::CreateAsFetch(
aReferrerPolicy);
}
PreloadHashKey PreloadHashKey::CreateAsFont(
nsIURI* aURI, const CORSMode aCORSMode,
const dom::ReferrerPolicy& aReferrerPolicy) {
PreloadHashKey key(aURI, ResourceType::FONT);
key.mReferrerPolicy = aReferrerPolicy;
key.mCORSMode = aCORSMode;
return key;
}
bool PreloadHashKey::KeyEquals(KeyTypePointer aOther) const {
if (mAs != aOther->mAs || mCORSMode != aOther->mCORSMode ||
mReferrerPolicy != aOther->mReferrerPolicy) {

View File

@ -69,8 +69,10 @@ class PreloadHashKey : public nsURIHashKey {
nsIURI* aURI, const nsAString& aCrossOrigin,
const dom::ReferrerPolicy& aReferrerPolicy);
// TODO
// static CreateAsFont(...);
// Construct key for "font"
static PreloadHashKey CreateAsFont(
nsIURI* aURI, const CORSMode aCORSMode,
const dom::ReferrerPolicy& aReferrerPolicy);
KeyType GetKey() const { return const_cast<PreloadHashKey*>(this); }
KeyTypePointer GetKeyPointer() const { return this; }

View File

@ -9,6 +9,7 @@
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/dom/HTMLLinkElement.h"
#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/FontPreloader.h"
#include "mozilla/StaticPrefs_network.h"
#include "nsIReferrerInfo.h"
#include "nsNetUtil.h"
@ -114,6 +115,9 @@ already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
preloadKey = PreloadHashKey::CreateAsImage(
uri, mDocument->NodePrincipal(),
dom::Element::StringToCORSMode(crossOrigin), referrerPolicy);
} else if (as.LowerCaseEqualsASCII("font")) {
preloadKey = PreloadHashKey::CreateAsFont(
uri, dom::Element::StringToCORSMode(crossOrigin), referrerPolicy);
} else if (as.LowerCaseEqualsASCII("fetch")) {
preloadKey = PreloadHashKey::CreateAsFetch(
uri, dom::Element::StringToCORSMode(crossOrigin), referrerPolicy);
@ -131,6 +135,8 @@ already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
PreloadStyle(uri, charset, crossOrigin, referrerPolicyAttr, integrity);
} else if (as.LowerCaseEqualsASCII("image")) {
PreloadImage(uri, crossOrigin, referrerPolicyAttr, isImgSet);
} else if (as.LowerCaseEqualsASCII("font")) {
PreloadFont(uri, crossOrigin, referrerPolicyAttr);
} else if (as.LowerCaseEqualsASCII("fetch")) {
PreloadFetch(uri, crossOrigin, referrerPolicyAttr);
}
@ -175,6 +181,19 @@ void PreloadService::PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
aIsImgSet, true);
}
void PreloadService::PreloadFont(nsIURI* aURI, const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy) {
CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
auto key = PreloadHashKey::CreateAsFont(aURI, cors, referrerPolicy);
// * Bug 1618549: Depending on where we decide to do the deduplication, we may
// want to check if the font is already being preloaded here.
RefPtr<FontPreloader> preloader = new FontPreloader();
preloader->OpenChannel(&key, aURI, cors, referrerPolicy, mDocument);
}
void PreloadService::PreloadFetch(nsIURI* aURI, const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy) {
CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);

View File

@ -75,6 +75,9 @@ class PreloadService {
void PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
const nsAString& aImageReferrerPolicy, bool aIsImgSet);
void PreloadFont(nsIURI* aURI, const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy);
void PreloadFetch(nsIURI* aURI, const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy);