Bug 935862 p3 - implement selective reflow for downloadable fonts. r=heycam
authorJohn Daggett <jdaggett@mozilla.com>
Fri, 06 Mar 2015 17:44:23 +0900
changeset 232175 aa4d3f86cfff0805c89ae33eeba05e8c25e6584b
parent 232174 5d45de55087d27c397c8f73b5bab370c7e1f4375
child 232176 cab9f41173d9bbc45ba0b84543cb245635bfb949
push id56479
push userjdaggett@mozilla.com
push dateFri, 06 Mar 2015 08:49:41 +0000
treeherdermozilla-inbound@70371a23e8dc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs935862
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 935862 p3 - implement selective reflow for downloadable fonts. r=heycam
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/style/moz.build
layout/style/nsFontFaceLoader.cpp
layout/style/nsFontFaceLoader.h
layout/style/nsFontFaceUtils.cpp
layout/style/nsFontFaceUtils.h
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -61,16 +61,17 @@
 #include "gfxPrefs.h"
 #include "nsIDOMChromeWindow.h"
 #include "nsFrameLoader.h"
 #include "mozilla/dom/FontFaceSet.h"
 #include "nsContentUtils.h"
 #include "nsPIWindowRoot.h"
 #include "mozilla/Preferences.h"
 #include "gfxTextRun.h"
+#include "nsFontFaceUtils.h"
 
 // Needed for Start/Stop of Image Animation
 #include "imgIContainer.h"
 #include "nsIImageLoadingContent.h"
 
 #include "nsCSSParser.h"
 #include "nsBidiUtils.h"
 #include "nsServiceManagerUtils.h"
@@ -2140,34 +2141,57 @@ nsPresContext::RebuildUserFontSet()
       NS_NewRunnableMethod(this, &nsPresContext::HandleRebuildUserFontSet);
     if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
       mPostedFlushUserFontSet = true;
     }
   }
 }
 
 void
-nsPresContext::UserFontSetUpdated()
+nsPresContext::UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont)
 {
   if (!mShell)
     return;
 
-  // Changes to the set of available fonts can cause updates to layout by:
-  //
-  //   1. Changing the font used for text, which changes anything that
-  //      depends on text measurement, including line breaking and
-  //      intrinsic widths, and any other parts of layout that depend on
-  //      font metrics.  This requires a style change reflow to update.
-  //
-  //   2. Changing the value of the 'ex' and 'ch' units in style data,
-  //      which also depend on font metrics.  Updating this information
-  //      requires rebuilding the rule tree from the top, avoiding the
-  //      reuse of cached data even when no style rules have changed.
-
-  PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, eRestyle_ForceDescendants);
+  bool usePlatformFontList = true;
+#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
+  usePlatformFontList = false;
+#endif
+
+  // xxx - until the Linux platform font list is always used, use full
+  // restyle to force updates with gfxPangoFontGroup usage
+  // Note: this method is called without a font when rules in the userfont set
+  // are updated, which may occur during reflow as a result of the lazy
+  // initialization of the userfont set. It would be better to avoid a full
+  // restyle but until this method is only called outside of reflow, schedule a
+  // full restyle in these cases.
+  if (!usePlatformFontList || !aUpdatedFont) {
+    PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, eRestyle_ForceDescendants);
+    return;
+  }
+
+  // Special case - if either the 'ex' or 'ch' units are used, these
+  // depend upon font metrics. Updating this information requires
+  // rebuilding the rule tree from the top, avoiding the reuse of cached
+  // data even when no style rules have changed.
+
+  if (UsesExChUnits()) {
+    // xxx - dbaron said this should work but get ex/ch related reftest failures
+    // PostRebuildAllStyleDataEvent(nsChangeHint(0), eRestyle_ForceDescendants);
+    PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, eRestyle_ForceDescendants);
+    return;
+  }
+
+  // Iterate over the frame tree looking for frames associated with the
+  // downloadable font family in question. If a frame's nsStyleFont has
+  // the name, check the font group associated with the metrics to see if
+  // it contains that specific font (i.e. the one chosen within the family
+  // given the weight, width, and slant from the nsStyleFont). If it does,
+  // mark that frame dirty and skip inspecting its descendants.
+  nsFontFaceUtils::MarkDirtyForFontChange(mShell->GetRootFrame(), aUpdatedFont);
 }
 
 FontFaceSet*
 nsPresContext::Fonts()
 {
   if (!mFontFaceSet) {
     mFontFaceSet = new FontFaceSet(mDocument->GetInnerWindow(), this);
     GetUserFontSet();  // this will cause the user font set to be created/updated
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -51,16 +51,17 @@ class nsIContent;
 class nsIFrame;
 class nsFrameManager;
 class nsILinkHandler;
 class nsIAtom;
 class nsICSSPseudoComparator;
 struct nsStyleBackground;
 struct nsStyleBorder;
 class nsIRunnable;
+class gfxUserFontEntry;
 class gfxUserFontSet;
 class gfxTextPerfMetrics;
 struct nsFontFaceRuleContainer;
 class nsPluginFrame;
 class nsTransitionManager;
 class nsAnimationManager;
 class nsRefreshDriver;
 class nsIWidget;
@@ -885,17 +886,17 @@ public:
 #endif
 
   void FlushUserFontSet();
   void RebuildUserFontSet(); // asynchronously
 
   // Should be called whenever the set of fonts available in the user
   // font set changes (e.g., because a new font loads, or because the
   // user font set is changed and fonts become unavailable).
-  void UserFontSetUpdated();
+  void UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont = nullptr);
 
   gfxMissingFontRecorder *MissingFontRecorder() { return mMissingFonts; }
   void NotifyMissingFonts();
 
   mozilla::dom::FontFaceSet* Fonts();
 
   void FlushCounterStyles();
   void RebuildCounterStyles(); // asynchronously
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -136,16 +136,17 @@ UNIFIED_SOURCES += [
     'nsCSSScanner.cpp',
     'nsCSSValue.cpp',
     'nsDOMCSSAttrDeclaration.cpp',
     'nsDOMCSSDeclaration.cpp',
     'nsDOMCSSRect.cpp',
     'nsDOMCSSRGBColor.cpp',
     'nsDOMCSSValueList.cpp',
     'nsFontFaceLoader.cpp',
+    'nsFontFaceUtils.cpp',
     'nsHTMLCSSStyleSheet.cpp',
     'nsHTMLStyleSheet.cpp',
     'nsLayoutStylesheetCache.cpp',
     'nsMediaFeatures.cpp',
     'nsNthIndexCache.cpp',
     'nsROCSSPrimitiveValue.cpp',
     'nsRuleData.cpp',
     'nsRuleNode.cpp',
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -116,17 +116,17 @@ nsFontFaceLoader::LoadTimerCallback(nsIT
   // before, we mark this entry as "loading slowly", so the fallback
   // font will be used in the meantime, and tell the context to refresh.
   if (updateUserFontSet) {
     ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
     nsPresContext* ctx = loader->mFontFaceSet->GetPresContext();
     NS_ASSERTION(ctx, "userfontset doesn't have a presContext?");
     if (ctx) {
       loader->mFontFaceSet->IncrementGeneration();
-      ctx->UserFontSetUpdated();
+      ctx->UserFontSetUpdated(loader->GetUserFontEntry());
       LOG(("userfonts (%p) timeout reflow\n", loader));
     }
   }
 }
 
 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver)
 
 NS_IMETHODIMP
@@ -188,17 +188,17 @@ nsFontFaceLoader::OnStreamComplete(nsISt
   // and we need to load the next source.
   bool fontUpdate =
     mUserFontEntry->FontDataDownloadComplete(aString, aStringLen, aStatus);
 
   // when new font loaded, need to reflow
   if (fontUpdate) {
     // Update layout for the presence of the new font.  Since this is
     // asynchronous, reflows will coalesce.
-    ctx->UserFontSetUpdated();
+    ctx->UserFontSetUpdated(mUserFontEntry);
     LOG(("userfonts (%p) reflow\n", this));
   }
 
   // done with font set
   mFontFaceSet = nullptr;
   if (mLoadTimer) {
     mLoadTimer->Cancel();
     mLoadTimer = nullptr;
--- a/layout/style/nsFontFaceLoader.h
+++ b/layout/style/nsFontFaceLoader.h
@@ -40,16 +40,17 @@ public:
 
   void StartedLoading(nsIStreamLoader* aStreamLoader);
 
   static void LoadTimerCallback(nsITimer* aTimer, void* aClosure);
 
   static nsresult CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
                                    nsIURI* aTargetURI,
                                    nsISupports* aContext);
+  gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; }
 
 protected:
   virtual ~nsFontFaceLoader();
 
 private:
   nsRefPtr<gfxUserFontEntry>  mUserFontEntry;
   nsCOMPtr<nsIURI>        mFontURI;
   nsRefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
new file mode 100644
--- /dev/null
+++ b/layout/style/nsFontFaceUtils.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* 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 "gfxUserFontSet.h"
+#include "nsFontFaceUtils.h"
+#include "nsFontMetrics.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTArray.h"
+
+static bool StyleContextContainsFont(nsStyleContext* aStyleContext,
+                                     const gfxUserFontSet* aUserFontSet,
+                                     const gfxUserFontEntry* aFont)
+{
+  // if the font is null, simply check to see whether fontlist includes
+  // downloadable fonts
+  if (!aFont) {
+    const FontFamilyList& fontlist =
+      aStyleContext->StyleFont()->mFont.fontlist;
+    return aUserFontSet->ContainsUserFontSetFonts(fontlist);
+  }
+
+  // first, check if the family name is in the fontlist
+  const nsString& familyName = aFont->FamilyName();
+  if (!aStyleContext->StyleFont()->mFont.fontlist.Contains(familyName)) {
+    return false;
+  }
+
+  // family name is in the fontlist, check to see if the font group
+  // associated with the frame includes the specific userfont
+  nsRefPtr<nsFontMetrics> fm;
+  nsLayoutUtils::GetFontMetricsForStyleContext(aStyleContext,
+                                               getter_AddRefs(fm),
+                                               1.0f);
+
+  if (fm->GetThebesFontGroup()->ContainsUserFont(aFont)) {
+    return true;
+  }
+
+  return false;
+}
+
+static bool
+FrameUsesFont(nsIFrame* aFrame, const gfxUserFontEntry* aFont)
+{
+  // check the style context of the frame
+  gfxUserFontSet* ufs = aFrame->PresContext()->GetUserFontSet();
+  if (StyleContextContainsFont(aFrame->StyleContext(), ufs, aFont)) {
+    return true;
+  }
+
+  // check additional style contexts
+  int32_t contextIndex = 0;
+  for (nsStyleContext* extraContext;
+       (extraContext = aFrame->GetAdditionalStyleContext(contextIndex));
+       ++contextIndex) {
+    if (StyleContextContainsFont(extraContext, ufs, aFont)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/* static */ void
+nsFontFaceUtils::MarkDirtyForFontChange(nsIFrame* aSubtreeRoot,
+                                        const gfxUserFontEntry* aFont)
+{
+  nsAutoTArray<nsIFrame*, 4> subtrees;
+  subtrees.AppendElement(aSubtreeRoot);
+
+  nsIPresShell* ps = aSubtreeRoot->PresContext()->PresShell();
+
+  // check descendants, iterating over subtrees that may include
+  // additional subtrees associated with placeholders
+  do {
+    nsIFrame* subtreeRoot = subtrees.ElementAt(subtrees.Length() - 1);
+    subtrees.RemoveElementAt(subtrees.Length() - 1);
+
+    // Check all descendants to see if they use the font
+    nsAutoTArray<nsIFrame*, 32> stack;
+    stack.AppendElement(subtreeRoot);
+
+    do {
+      nsIFrame* f = stack.ElementAt(stack.Length() - 1);
+      stack.RemoveElementAt(stack.Length() - 1);
+
+      // if this frame uses the font, mark its descendants dirty
+      // and skip checking its children
+      if (FrameUsesFont(f, aFont)) {
+        ps->FrameNeedsReflow(f, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+      } else {
+        if (f->GetType() == nsGkAtoms::placeholderFrame) {
+          nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
+          if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
+            // We have another distinct subtree we need to mark.
+            subtrees.AppendElement(oof);
+          }
+        }
+
+        nsIFrame::ChildListIterator lists(f);
+        for (; !lists.IsDone(); lists.Next()) {
+          nsFrameList::Enumerator childFrames(lists.CurrentList());
+          for (; !childFrames.AtEnd(); childFrames.Next()) {
+            nsIFrame* kid = childFrames.get();
+            stack.AppendElement(kid);
+          }
+        }
+      }
+    } while (!stack.IsEmpty());
+  } while (!subtrees.IsEmpty());
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/nsFontFaceUtils.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* 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/. */
+
+/* helper utilities for working with downloadable fonts */
+
+#ifndef nsFontFaceUtils_h_
+#define nsFontFaceUtils_h_
+
+class gfxUserFontEntry;
+class nsIFrame;
+
+class nsFontFaceUtils
+{
+public:
+  // mark dirty frames affected by a downloadable font
+  static void MarkDirtyForFontChange(nsIFrame* aSubtreeRoot,
+                                     const gfxUserFontEntry* aFont);
+};
+
+#endif /* !defined(nsFontFaceUtils_h_) */