Bug 1376964 - Part 4: Call FontLoadAllowed ahead of time and cache the results for style worker threads. r=jfkthame draft
authorCameron McCormack <cam@mcc.id.au>
Fri, 07 Jul 2017 13:35:28 +0800
changeset 608076 c42276ebd5fad32867478582b80501153dd950b8
parent 608075 6c4e8aeabd39e6c0b2841de72aca8f2e011a806d
child 608077 77e116bc72e976f3b94ec8f003aae3d19c2e3ce4
push id68174
push userbmo:cam@mcc.id.au
push dateThu, 13 Jul 2017 06:08:51 +0000
reviewersjfkthame
bugs1376964
milestone56.0a1
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
gfx/thebes/gfxUserFontSet.cpp
gfx/thebes/gfxUserFontSet.h
layout/style/FontFaceSet.cpp
layout/style/FontFaceSet.h
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
--- 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;