Bug 1376964 - Part 4: Call FontLoadAllowed ahead of time and cache the results for style worker threads. r=jfkthame
Handling a document's node principal changing is done in part 9.
MozReview-Commit-ID: 1gPtRpddys5
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -904,16 +904,18 @@ gfxUserFontSet::gfxUserFontSet()
}
gfxUserFontSet::~gfxUserFontSet()
{
gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
if (fp) {
fp->RemoveUserFontSet(this);
}
+
+ UserFontCache::ClearAllowedFontSets(this);
}
already_AddRefed<gfxUserFontEntry>
gfxUserFontSet::FindOrCreateUserFontEntry(
const nsAString& aFamilyName,
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
uint32_t aWeight,
int32_t aStretch,
@@ -1274,51 +1276,144 @@ gfxUserFontSet::UserFontCache::GetFont(n
gfxUserFontEntry* aUserFontEntry,
bool aPrivate)
{
if (!sUserFonts ||
Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
return nullptr;
}
- // We have to perform another content policy check here to prevent
- // cache poisoning. E.g. a.com loads a font into the cache but
- // b.com has a CSP not allowing any fonts to be loaded.
- if (!aUserFontEntry->mFontSet->IsFontLoadAllowed(aSrcURI, aPrincipal)) {
- return nullptr;
- }
-
// Ignore principal when looking up a data: URI.
nsIPrincipal* principal;
if (IgnorePrincipal(aSrcURI)) {
principal = nullptr;
} else {
principal = aPrincipal;
}
Entry* entry = sUserFonts->GetEntry(Key(aSrcURI, principal, aUserFontEntry,
aPrivate));
- if (entry) {
- return entry->GetFontEntry();
+ if (!entry) {
+ return nullptr;
+ }
+
+ // We have to perform another content policy check here to prevent
+ // cache poisoning. E.g. a.com loads a font into the cache but
+ // b.com has a CSP not allowing any fonts to be loaded.
+ bool allowed = false;
+ if (ServoStyleSet::IsInServoTraversal()) {
+ // Use the cached IsFontLoadAllowed results in mAllowedFontSets.
+ allowed = entry->IsFontSetAllowed(aUserFontEntry->mFontSet);
+ } else {
+ // Call IsFontLoadAllowed directly, since we are on the main thread.
+ MOZ_ASSERT(NS_IsMainThread());
+ allowed = aUserFontEntry->mFontSet->IsFontLoadAllowed(aSrcURI,
+ aPrincipal);
+ MOZ_ASSERT(!entry->IsFontSetAllowedKnown(aUserFontEntry->mFontSet) ||
+ entry->IsFontSetAllowed(aUserFontEntry->mFontSet) == allowed,
+ "why does IsFontLoadAllowed return a different value from "
+ "the cached value in mAllowedFontSets?");
+ }
+
+ if (!allowed) {
+ return nullptr;
}
- return nullptr;
+ return entry->GetFontEntry();
+}
+
+/* static */ void
+gfxUserFontSet::UserFontCache::UpdateAllowedFontSets(
+ gfxUserFontSet* aUserFontSet)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sUserFonts) {
+ return;
+ }
+
+ for (auto iter = sUserFonts->Iter(); !iter.Done(); iter.Next()) {
+ Entry* entry = iter.Get();
+ if (!entry->IsFontSetAllowedKnown(aUserFontSet)) {
+ nsIPrincipal* principal = entry->GetPrincipal();
+ if (!principal) {
+ // This is a data: URI. Just get the standard principal the
+ // font set uses. (For cases when mUseOriginPrincipal is true,
+ // we don't use the cached results of IsFontLoadAllowed, and
+ // instead just process the data: URI load async.)
+ principal = aUserFontSet->GetStandardFontLoadPrincipal();
+ }
+ bool allowed =
+ aUserFontSet->IsFontLoadAllowed(entry->GetURI(), principal);
+ entry->SetIsFontSetAllowed(aUserFontSet, allowed);
+ }
+ }
+}
+
+/* static */ void
+gfxUserFontSet::UserFontCache::ClearAllowedFontSets(
+ gfxUserFontSet* aUserFontSet)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sUserFonts) {
+ return;
+ }
+
+ for (auto iter = sUserFonts->Iter(); !iter.Done(); iter.Next()) {
+ Entry* entry = iter.Get();
+ entry->ClearIsFontSetAllowed(aUserFontSet);
+ }
}
void
gfxUserFontSet::UserFontCache::Shutdown()
{
if (sUserFonts) {
delete sUserFonts;
sUserFonts = nullptr;
}
}
MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf)
+bool
+gfxUserFontSet::UserFontCache::Entry::IsFontSetAllowed(
+ gfxUserFontSet* aUserFontSet) const
+{
+ bool allowed = false;
+ DebugOnly<bool> found = mAllowedFontSets.Get(aUserFontSet, &allowed);
+ MOZ_ASSERT(found, "UpdateAllowedFontSets should have been called and "
+ "added an entry to mAllowedFontSets");
+ return allowed;
+}
+
+bool
+gfxUserFontSet::UserFontCache::Entry::IsFontSetAllowedKnown(
+ gfxUserFontSet* aUserFontSet) const
+{
+ return mAllowedFontSets.Contains(aUserFontSet);
+}
+
+void
+gfxUserFontSet::UserFontCache::Entry::SetIsFontSetAllowed(
+ gfxUserFontSet* aUserFontSet,
+ bool aAllowed)
+{
+ MOZ_ASSERT(!IsFontSetAllowedKnown(aUserFontSet));
+ mAllowedFontSets.Put(aUserFontSet, aAllowed);
+}
+
+void
+gfxUserFontSet::UserFontCache::Entry::ClearIsFontSetAllowed(
+ gfxUserFontSet* aUserFontSet)
+{
+ mAllowedFontSets.Remove(aUserFontSet);
+}
+
void
gfxUserFontSet::UserFontCache::Entry::ReportMemory(
nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize)
{
MOZ_ASSERT(mFontEntry);
nsAutoCString path("explicit/gfx/user-fonts/font(");
if (aAnonymize) {
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -251,16 +251,18 @@ public:
// check whether the given source is allowed to be loaded;
// returns the Principal (for use in the key when caching the loaded font),
// and whether the load should bypass the cache (force-reload).
virtual nsresult CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
nsIPrincipal** aPrincipal,
bool* aBypassCache) = 0;
+ virtual nsIPrincipal* GetStandardFontLoadPrincipal() = 0;
+
// check whether content policies allow the given URI to load.
virtual bool IsFontLoadAllowed(nsIURI* aFontLocation,
nsIPrincipal* aPrincipal) = 0;
// initialize the process that loads external font data, which upon
// completion will call FontDataDownloadComplete method
virtual nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
const gfxFontFaceSrc* aFontFaceSrc) = 0;
@@ -300,16 +302,35 @@ public:
nsIPrincipal* aPrincipal,
gfxUserFontEntry* aUserFontEntry,
bool aPrivate);
// Generation number that is incremented whenever an entry is added to
// the cache. (Removals don't increment it.)
static uint32_t Generation() { return sGeneration; }
+ // For each entry in the user font cache where we haven't recorded
+ // whether the given user font set is allowed to use the entry,
+ // call IsFontLoadAllowed and record it.
+ //
+ // This function should be called just before a Servo restyle, so
+ // that we can determine whether a given font load (using a cached
+ // font) would be allowed without having to call the non-OMT-safe
+ // IsFontLoadAllowed from the style worker threads.
+ static void UpdateAllowedFontSets(gfxUserFontSet* aUserFontSet);
+
+ // Clears all recorded IsFontLoadAllowed results for the given
+ // user font set.
+ //
+ // This function should be called just before the user font set is
+ // going away, or when we detect that a document's node principal
+ // has changed (and thus the already recorded IsFontLoadAllowed
+ // results are no longer valid).
+ static void ClearAllowedFontSets(gfxUserFontSet* aUserFontSet);
+
// Clear everything so that we don't leak URIs and Principals.
static void Shutdown();
// Memory-reporting support.
class MemoryReporter final : public nsIMemoryReporter
{
private:
~MemoryReporter() { }
@@ -396,34 +417,54 @@ public:
(aKey->mFontEntry->mStyle |
(aKey->mFontEntry->mWeight << 2) |
(aKey->mFontEntry->mStretch << 11) ) ^
aKey->mFontEntry->mLanguageOverride);
}
enum { ALLOW_MEMMOVE = false };
+ nsIURI* GetURI() const { return mURI; }
+ nsIPrincipal* GetPrincipal() const { return mPrincipal; }
gfxFontEntry* GetFontEntry() const { return mFontEntry; }
+ bool IsPrivate() const { return mPrivate; }
- bool IsPrivate() const { return mPrivate; }
+ bool IsFontSetAllowed(gfxUserFontSet* aUserFontSet) const;
+ bool IsFontSetAllowedKnown(gfxUserFontSet* aUserFontSet) const;
+ void SetIsFontSetAllowed(gfxUserFontSet* aUserFontSet, bool aAllowed);
+ void ClearIsFontSetAllowed(gfxUserFontSet* aUserFontSet);
void ReportMemory(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize);
#ifdef DEBUG_USERFONT_CACHE
void Dump();
#endif
private:
static uint32_t
HashFeatures(const nsTArray<gfxFontFeature>& aFeatures) {
return mozilla::HashBytes(aFeatures.Elements(),
aFeatures.Length() * sizeof(gfxFontFeature));
}
+ // Set of gfxUserFontSets that are allowed to use this cached font
+ // entry.
+ //
+ // This is basically a cache of results of calls to
+ // gfxUserFontSet::IsFontLoadAllowed for each font set to be used
+ // when using the cache from style worker threads (where calling
+ // IsFontLoadAllowed is not possible). Whenever a new entry is
+ // added to the cache, sGeneration is bumped, and a FontFaceSet
+ // for a document about to be styled can call UpdateAllowedFontSets
+ // to record IsFontLoadAllowed results for the new entries. When
+ // a FontFaceSet is going away, it calls ClearAllowedFontSets
+ // to remove entries from the mAllowedFontSets tables.
+ nsDataHashtable<nsPtrHashKey<gfxUserFontSet>, bool> mAllowedFontSets;
+
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIPrincipal> mPrincipal; // or nullptr for data: URLs
// The "real" font entry corresponding to this downloaded font.
// The font entry MUST notify the cache when it is destroyed
// (by calling ForgetFont()).
gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry;
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -1321,16 +1321,22 @@ FontFaceSet::LogMessage(gfxUserFontEntry
innerWindowID);
if (NS_SUCCEEDED(rv)) {
console->LogMessage(scriptError);
}
return NS_OK;
}
+nsIPrincipal*
+FontFaceSet::GetStandardFontLoadPrincipal()
+{
+ return mDocument->NodePrincipal();
+}
+
nsresult
FontFaceSet::CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
nsIPrincipal** aPrincipal,
bool* aBypassCache)
{
NS_ASSERTION(aFontFaceSrc &&
aFontFaceSrc->mSourceType == gfxFontFaceSrc::eSourceType_URL,
"bad font face url passed to fontloader");
@@ -1339,17 +1345,17 @@ FontFaceSet::CheckFontLoad(const gfxFont
NS_ASSERTION(aFontFaceSrc->mURI, "null font uri");
if (!aFontFaceSrc->mURI)
return NS_ERROR_FAILURE;
// use document principal, original principal if flag set
// this enables user stylesheets to load font files via
// @font-face rules
- *aPrincipal = mDocument->NodePrincipal();
+ *aPrincipal = GetStandardFontLoadPrincipal();
NS_ASSERTION(aFontFaceSrc->mOriginPrincipal,
"null origin principal in @font-face rule");
if (aFontFaceSrc->mUseOriginPrincipal) {
*aPrincipal = aFontFaceSrc->mOriginPrincipal;
}
*aBypassCache = mBypassCache;
@@ -1792,16 +1798,25 @@ FontFaceSet::UserFontSet::CheckFontLoad(
bool* aBypassCache)
{
if (!mFontFaceSet) {
return NS_ERROR_FAILURE;
}
return mFontFaceSet->CheckFontLoad(aFontFaceSrc, aPrincipal, aBypassCache);
}
+/* virtual */ nsIPrincipal*
+FontFaceSet::UserFontSet::GetStandardFontLoadPrincipal()
+{
+ if (!mFontFaceSet) {
+ return nullptr;
+ }
+ return mFontFaceSet->GetStandardFontLoadPrincipal();
+}
+
/* virtual */ bool
FontFaceSet::UserFontSet::IsFontLoadAllowed(nsIURI* aFontLocation,
nsIPrincipal* aPrincipal)
{
return mFontFaceSet &&
mFontFaceSet->IsFontLoadAllowed(aFontLocation, aPrincipal);
}
--- a/layout/style/FontFaceSet.h
+++ b/layout/style/FontFaceSet.h
@@ -58,16 +58,18 @@ public:
public:
explicit UserFontSet(FontFaceSet* aFontFaceSet)
: mFontFaceSet(aFontFaceSet)
{
}
FontFaceSet* GetFontFaceSet() { return mFontFaceSet; }
+ nsIPrincipal* GetStandardFontLoadPrincipal() override;
+
virtual nsresult CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
nsIPrincipal** aPrincipal,
bool* aBypassCache) override;
virtual bool IsFontLoadAllowed(nsIURI* aFontLocation,
nsIPrincipal* aPrincipal) override;
virtual nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
@@ -256,16 +258,17 @@ private:
FontFace* aFontFace,
SheetType aSheetType);
// search for @font-face rule that matches a userfont font entry
nsCSSFontFaceRule* FindRuleForUserFontEntry(gfxUserFontEntry* aUserFontEntry);
nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
const gfxFontFaceSrc* aFontFaceSrc);
+ nsIPrincipal* GetStandardFontLoadPrincipal();
nsresult CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
nsIPrincipal** aPrincipal,
bool* aBypassCache);
bool IsFontLoadAllowed(nsIURI* aFontLocation, nsIPrincipal* aPrincipal);
nsresult SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
const gfxFontFaceSrc* aFontFaceSrc,
uint8_t*& aBuffer,
uint32_t& aBufferLength);
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -35,16 +35,17 @@
using namespace mozilla;
using namespace mozilla::dom;
ServoStyleSet::ServoStyleSet()
: mPresContext(nullptr)
, mAuthorStyleDisabled(false)
, mStylistState(StylistState::NotDirty)
, mUserFontSetUpdateGeneration(0)
+ , mUserFontCacheUpdateGeneration(0)
, mNeedsRestyleAfterEnsureUniqueInner(false)
{
}
ServoStyleSet::~ServoStyleSet()
{
for (auto& sheetArray : mSheets) {
for (auto& sheet : sheetArray) {
@@ -293,23 +294,31 @@ ServoStyleSet::PreTraverseSync()
ResolveMappedAttrDeclarationBlocks();
nsCSSRuleProcessor::InitSystemMetrics();
// This is lazily computed and pseudo matching needs to access
// it so force computation early.
mPresContext->Document()->GetDocumentState();
- // Ensure that the @font-face data is not stale
if (gfxUserFontSet* userFontSet = mPresContext->Document()->GetUserFontSet()) {
+ // Ensure that the @font-face data is not stale
uint64_t generation = userFontSet->GetGeneration();
if (generation != mUserFontSetUpdateGeneration) {
mPresContext->DeviceContext()->UpdateFontCacheUserFonts(userFontSet);
mUserFontSetUpdateGeneration = generation;
}
+
+ // Ensure that the user font cache holds up-to-date data on whether
+ // our font set is allowed to re-use fonts from the cache.
+ uint32_t cacheGeneration = gfxUserFontSet::UserFontCache::Generation();
+ if (cacheGeneration != mUserFontCacheUpdateGeneration) {
+ gfxUserFontSet::UserFontCache::UpdateAllowedFontSets(userFontSet);
+ mUserFontCacheUpdateGeneration = cacheGeneration;
+ }
}
UpdateStylistIfNeeded();
mPresContext->CacheAllLangs();
}
void
ServoStyleSet::PreTraverse(Element* aRoot,
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -578,16 +578,17 @@ private:
nsPresContext* mPresContext;
UniquePtr<RawServoStyleSet> mRawSet;
EnumeratedArray<SheetType, SheetType::Count,
nsTArray<RefPtr<ServoStyleSheet>>> mSheets;
bool mAuthorStyleDisabled;
StylistState mStylistState;
uint64_t mUserFontSetUpdateGeneration;
+ uint32_t mUserFontCacheUpdateGeneration;
bool mNeedsRestyleAfterEnsureUniqueInner;
// Stores pointers to our cached style contexts for non-inheriting anonymous
// boxes.
EnumeratedArray<nsCSSAnonBoxes::NonInheriting,
nsCSSAnonBoxes::NonInheriting::_Count,
RefPtr<nsStyleContext>> mNonInheritingStyleContexts;