Bug 619521 - Part 1: Send a notification of any scripts for which font coverage is lacking. r=jdaggett
authorJonathan Kew <jkew@mozilla.com>
Mon, 22 Dec 2014 16:35:54 +0000
changeset 248070 dc9924bb2e781a92faa2f921d9201f1d3fdf0bdd
parent 248069 a7e5b17a0e4bc3b396008d7f11cda9cef4c8bdb2
child 248071 25436150600c2de2c0ae52a37b873fcfbd28cd66
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdaggett
bugs619521
milestone37.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 619521 - Part 1: Send a notification of any scripts for which font coverage is lacking. r=jdaggett
dom/canvas/CanvasRenderingContext2D.cpp
gfx/src/nsFontMetrics.cpp
gfx/thebes/gfxTextRun.cpp
gfx/thebes/gfxTextRun.h
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/base/nsPresShell.cpp
layout/generic/MathMLTextRunFactory.cpp
layout/generic/MathMLTextRunFactory.h
layout/generic/nsTextFrame.cpp
layout/generic/nsTextRunTransformations.cpp
layout/generic/nsTextRunTransformations.h
layout/mathml/nsMathMLChar.cpp
modules/libpref/init/all.js
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3247,33 +3247,50 @@ CanvasRenderingContext2D::GetHitRegionRe
   return false;
 }
 
 /**
  * Used for nsBidiPresUtils::ProcessText
  */
 struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
 {
+  CanvasBidiProcessor()
+    : nsBidiPresUtils::BidiProcessor()
+  {
+    if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
+      mMissingFonts = new gfxMissingFontRecorder();
+    }
+  }
+
+  ~CanvasBidiProcessor()
+  {
+    // notify front-end code if we encountered missing glyphs in any script
+    if (mMissingFonts) {
+      mMissingFonts->Flush();
+    }
+  }
+
   typedef CanvasRenderingContext2D::ContextState ContextState;
 
   virtual void SetText(const char16_t* text, int32_t length, nsBidiDirection direction)
   {
     mFontgrp->UpdateUserFonts(); // ensure user font generation is current
     // adjust flags for current direction run
     uint32_t flags = mTextRunFlags;
     if (direction == NSBIDI_RTL) {
       flags |= gfxTextRunFactory::TEXT_IS_RTL;
     } else {
       flags &= ~gfxTextRunFactory::TEXT_IS_RTL;
     }
     mTextRun = mFontgrp->MakeTextRun(text,
                                      length,
                                      mThebes,
                                      mAppUnitsPerDevPixel,
-                                     flags);
+                                     flags,
+                                     mMissingFonts);
   }
 
   virtual nscoord GetWidth()
   {
     gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
                                                                mTextRun->GetLength(),
                                                                mDoMeasureBoundingBox ?
                                                                  gfxFont::TIGHT_INK_EXTENTS :
@@ -3509,16 +3526,20 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
   CanvasRenderingContext2D *mCtx;
 
   // position of the left side of the string, alphabetic baseline
   gfxPoint mPt;
 
   // current font
   gfxFontGroup* mFontgrp;
 
+  // to record any unsupported characters found in the text,
+  // and notify front-end if it is interested
+  nsAutoPtr<gfxMissingFontRecorder> mMissingFonts;
+
   // dev pixel conversion factor
   int32_t mAppUnitsPerDevPixel;
 
   // operation (fill or stroke)
   CanvasRenderingContext2D::TextDrawOperation mOp;
 
   // context state
   ContextState *mState;
--- a/gfx/src/nsFontMetrics.cpp
+++ b/gfx/src/nsFontMetrics.cpp
@@ -28,27 +28,29 @@ class AutoTextRun {
 public:
     AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC,
                 const char* aString, int32_t aLength)
     {
         mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
             reinterpret_cast<const uint8_t*>(aString), aLength,
             aRC->ThebesContext(),
             aMetrics->AppUnitsPerDevPixel(),
-            ComputeFlags(aMetrics));
+            ComputeFlags(aMetrics),
+            nullptr);
     }
 
     AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC,
                 const char16_t* aString, int32_t aLength)
     {
         mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
             aString, aLength,
             aRC->ThebesContext(),
             aMetrics->AppUnitsPerDevPixel(),
-            ComputeFlags(aMetrics));
+            ComputeFlags(aMetrics),
+            nullptr);
     }
 
     gfxTextRun *get() { return mTextRun; }
     gfxTextRun *operator->() { return mTextRun; }
 
 private:
     static uint32_t ComputeFlags(nsFontMetrics* aMetrics) {
         uint32_t flags = 0;
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -23,16 +23,17 @@
 #include "mozilla/Likely.h"
 #include "gfx2DGlue.h"
 
 #include "cairo.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::unicode;
+using mozilla::services::GetObserverService;
 
 static const char16_t kEllipsisChar[] = { 0x2026, 0x0 };
 static const char16_t kASCIIPeriodsChar[] = { '.', '.', '.', 0x0 };
 
 #ifdef DEBUG_roc
 #define DEBUG_TEXT_RUN_STORAGE_METRICS
 #endif
 
@@ -2034,22 +2035,22 @@ gfxFontGroup::MakeHyphenTextRun(gfxConte
 {
     // only use U+2010 if it is supported by the first font in the group;
     // it's better to use ASCII '-' from the primary font than to fall back to
     // U+2010 from some other, possibly poorly-matching face
     static const char16_t hyphen = 0x2010;
     gfxFont *font = GetFirstValidFont(uint32_t(hyphen));
     if (font->HasCharacter(hyphen)) {
         return MakeTextRun(&hyphen, 1, aCtx, aAppUnitsPerDevUnit,
-                           gfxFontGroup::TEXT_IS_PERSISTENT);
+                           gfxFontGroup::TEXT_IS_PERSISTENT, nullptr);
     }
 
     static const uint8_t dash = '-';
     return MakeTextRun(&dash, 1, aCtx, aAppUnitsPerDevUnit,
-                       gfxFontGroup::TEXT_IS_PERSISTENT);
+                       gfxFontGroup::TEXT_IS_PERSISTENT, nullptr);
 }
 
 gfxFloat
 gfxFontGroup::GetHyphenWidth(gfxTextRun::PropertyProvider *aProvider)
 {
     if (mHyphenWidth < 0) {
         nsRefPtr<gfxContext> ctx(aProvider->GetContext());
         if (ctx) {
@@ -2060,17 +2061,18 @@ gfxFontGroup::GetHyphenWidth(gfxTextRun:
                 hyphRun->GetAdvanceWidth(0, hyphRun->GetLength(), nullptr) : 0;
         }
     }
     return mHyphenWidth;
 }
 
 gfxTextRun *
 gfxFontGroup::MakeTextRun(const uint8_t *aString, uint32_t aLength,
-                          const Parameters *aParams, uint32_t aFlags)
+                          const Parameters *aParams, uint32_t aFlags,
+                          gfxMissingFontRecorder *aMFR)
 {
     if (aLength == 0) {
         return MakeEmptyTextRun(aParams, aFlags);
     }
     if (aLength == 1 && aString[0] == ' ') {
         return MakeSpaceTextRun(aParams, aFlags);
     }
 
@@ -2084,26 +2086,27 @@ gfxFontGroup::MakeTextRun(const uint8_t 
     }
 
     gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength,
                                              this, aFlags);
     if (!textRun) {
         return nullptr;
     }
 
-    InitTextRun(aParams->mContext, textRun, aString, aLength);
+    InitTextRun(aParams->mContext, textRun, aString, aLength, aMFR);
 
     textRun->FetchGlyphExtents(aParams->mContext);
 
     return textRun;
 }
 
 gfxTextRun *
 gfxFontGroup::MakeTextRun(const char16_t *aString, uint32_t aLength,
-                          const Parameters *aParams, uint32_t aFlags)
+                          const Parameters *aParams, uint32_t aFlags,
+                          gfxMissingFontRecorder *aMFR)
 {
     if (aLength == 0) {
         return MakeEmptyTextRun(aParams, aFlags);
     }
     if (aLength == 1 && aString[0] == ' ') {
         return MakeSpaceTextRun(aParams, aFlags);
     }
     if (GetStyle()->size == 0) {
@@ -2111,29 +2114,30 @@ gfxFontGroup::MakeTextRun(const char16_t
     }
 
     gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength,
                                              this, aFlags);
     if (!textRun) {
         return nullptr;
     }
 
-    InitTextRun(aParams->mContext, textRun, aString, aLength);
+    InitTextRun(aParams->mContext, textRun, aString, aLength, aMFR);
 
     textRun->FetchGlyphExtents(aParams->mContext);
 
     return textRun;
 }
 
 template<typename T>
 void
 gfxFontGroup::InitTextRun(gfxContext *aContext,
                           gfxTextRun *aTextRun,
                           const T *aString,
-                          uint32_t aLength)
+                          uint32_t aLength,
+                          gfxMissingFontRecorder *aMFR)
 {
     NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run");
 
     // we need to do numeral processing even on 8-bit text,
     // in case we're converting Western to Hindi/Arabic digits
     int32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption();
     nsAutoArrayPtr<char16_t> transformedString;
     if (numOption != IBMBIDI_NUMERAL_NOMINAL) {
@@ -2203,17 +2207,17 @@ gfxFontGroup::InitTextRun(gfxContext *aC
                         sizeof(T),
                         str.get()));
             }
 #endif
 
             // the text is still purely 8-bit; bypass the script-run itemizer
             // and treat it as a single Latin run
             InitScriptRun(aContext, aTextRun, aString,
-                          0, aLength, MOZ_SCRIPT_LATIN);
+                          0, aLength, MOZ_SCRIPT_LATIN, aMFR);
         } else {
             const char16_t *textPtr;
             if (transformedString) {
                 textPtr = transformedString.get();
             } else {
                 // typecast to avoid compilation error for the 8-bit version,
                 // even though this is dead code in that case
                 textPtr = reinterpret_cast<const char16_t*>(aString);
@@ -2251,17 +2255,17 @@ gfxFontGroup::InitTextRun(gfxContext *aC
                                                                     "normal")),
                             mStyle.size,
                             sizeof(T),
                             NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
                 }
     #endif
 
                 InitScriptRun(aContext, aTextRun, textPtr + runStart,
-                              runStart, runLimit - runStart, runScript);
+                              runStart, runLimit - runStart, runScript, aMFR);
             }
         }
 
         // if shaping was aborted due to lack of feature support, clear out
         // glyph runs and redo shaping with fallback forced on
         if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) {
             redo = true;
             aTextRun->SetShapingState(
@@ -2287,26 +2291,35 @@ gfxFontGroup::InitTextRun(gfxContext *aC
     // pathologically-bad perf in the case where a textrun contains many script
     // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs
     // every time a new script subrun is processed.
     aTextRun->SanitizeGlyphRuns();
 
     aTextRun->SortGlyphRuns();
 }
 
+static inline bool
+IsPUA(uint32_t aUSV)
+{
+    // We could look up the General Category of the codepoint here,
+    // but it's simpler to check PUA codepoint ranges.
+    return (aUSV >= 0xE000 && aUSV <= 0xF8FF) || (aUSV >= 0xF0000);
+}
+
 template<typename T>
 void
 gfxFontGroup::InitScriptRun(gfxContext *aContext,
                             gfxTextRun *aTextRun,
                             const T *aString, // text for this script run,
                                               // not the entire textrun
                             uint32_t aOffset, // position of the script run
                                               // within the textrun
                             uint32_t aLength, // length of the script run
-                            int32_t aRunScript)
+                            int32_t aRunScript,
+                            gfxMissingFontRecorder *aMFR)
 {
     NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run");
     NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted,
                  "don't call InitScriptRun with aborted shaping state");
 
 #if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID)
     // non-linux platforms build the fontlist lazily and include userfonts
     // so need to confirm the load state of userfonts in the list
@@ -2317,16 +2330,17 @@ gfxFontGroup::InitScriptRun(gfxContext *
 
     gfxFont *mainFont = GetFirstValidFont();
 
     uint32_t runStart = 0;
     nsAutoTArray<gfxTextRange,3> fontRanges;
     ComputeRanges(fontRanges, aString, aLength, aRunScript,
                   aTextRun->GetFlags() & gfxTextRunFactory::TEXT_ORIENT_MASK);
     uint32_t numRanges = fontRanges.Length();
+    bool missingChars = false;
 
     for (uint32_t r = 0; r < numRanges; r++) {
         const gfxTextRange& range = fontRanges[r];
         uint32_t matchedLength = range.Length();
         gfxFont *matchedFont = range.font;
         bool vertical =
             range.orientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
         // create the glyph run for this range
@@ -2463,21 +2477,25 @@ gfxFontGroup::InitScriptRun(gfxContext *
 
                 // for 16-bit textruns only, check for surrogate pairs and
                 // special Unicode spaces; omit these checks in 8-bit runs
                 if (sizeof(T) == sizeof(char16_t)) {
                     if (NS_IS_HIGH_SURROGATE(ch) &&
                         index + 1 < aLength &&
                         NS_IS_LOW_SURROGATE(aString[index + 1]))
                     {
+                        uint32_t usv =
+                            SURROGATE_TO_UCS4(ch, aString[index + 1]);
                         aTextRun->SetMissingGlyph(aOffset + index,
-                                                  SURROGATE_TO_UCS4(ch,
-                                                                    aString[index + 1]),
+                                                  usv,
                                                   mainFont);
                         index++;
+                        if (!mSkipDrawing && !IsPUA(usv)) {
+                            missingChars = true;
+                        }
                         continue;
                     }
 
                     // check if this is a known Unicode whitespace character that
                     // we can render using the space glyph with a custom width
                     gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch);
                     if (wid >= 0.0) {
                         nscoord advance =
@@ -2502,21 +2520,28 @@ gfxFontGroup::InitScriptRun(gfxContext *
 
                 if (IsInvalidChar(ch)) {
                     // invalid chars are left as zero-width/invisible
                     continue;
                 }
 
                 // record char code so we can draw a box with the Unicode value
                 aTextRun->SetMissingGlyph(aOffset + index, ch, mainFont);
+                if (!mSkipDrawing && !IsPUA(ch)) {
+                    missingChars = true;
+                }
             }
         }
 
         runStart += matchedLength;
     }
+
+    if (aMFR && missingChars) {
+        aMFR->RecordScript(aRunScript);
+    }
 }
 
 gfxTextRun *
 gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel,
                                  LazyReferenceContextGetter& aRefContextGetter)
 {
     if (mCachedEllipsisTextRun &&
         mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) {
@@ -2532,17 +2557,18 @@ gfxFontGroup::GetEllipsisTextRun(int32_t
         : nsDependentString(kASCIIPeriodsChar,
                             ArrayLength(kASCIIPeriodsChar) - 1);
 
     nsRefPtr<gfxContext> refCtx = aRefContextGetter.GetRefContext();
     Parameters params = {
         refCtx, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel
     };
     gfxTextRun* textRun =
-        MakeTextRun(ellipsis.get(), ellipsis.Length(), &params, TEXT_IS_PERSISTENT);
+        MakeTextRun(ellipsis.get(), ellipsis.Length(), &params,
+                    TEXT_IS_PERSISTENT, nullptr);
     if (!textRun) {
         return nullptr;
     }
     mCachedEllipsisTextRun = textRun;
     textRun->ReleaseFontGroup(); // don't let the presence of a cached ellipsis
                                  // textrun prolong the fontgroup's life
     return textRun;
 }
@@ -3104,8 +3130,46 @@ gfxFontGroup::WhichSystemFontSupportsCha
 
 /*static*/ void
 gfxFontGroup::Shutdown()
 {
     NS_IF_RELEASE(gLangService);
 }
 
 nsILanguageAtomService* gfxFontGroup::gLangService = nullptr;
+
+void
+gfxMissingFontRecorder::Flush()
+{
+    static bool mNotifiedFontsInitialized = false;
+    static uint32_t mNotifiedFonts[gfxMissingFontRecorder::kNumScriptBitsWords];
+    if (!mNotifiedFontsInitialized) {
+        memset(&mNotifiedFonts, 0, sizeof(mNotifiedFonts));
+        mNotifiedFontsInitialized = true;
+    }
+
+    nsAutoString fontNeeded;
+    for (uint32_t i = 0; i < kNumScriptBitsWords; ++i) {
+        mMissingFonts[i] &= ~mNotifiedFonts[i];
+        if (!mMissingFonts[i]) {
+            continue;
+        }
+        for (uint32_t j = 0; j < 32; ++j) {
+            if (!(mMissingFonts[i] & (1 << j))) {
+                continue;
+            }
+            mNotifiedFonts[i] |= (1 << j);
+            if (!fontNeeded.IsEmpty()) {
+                fontNeeded.Append(PRUnichar(','));
+            }
+            uint32_t tag = GetScriptTagForCode(i * 32 + j);
+            fontNeeded.Append(char16_t(tag >> 24));
+            fontNeeded.Append(char16_t((tag >> 16) & 0xff));
+            fontNeeded.Append(char16_t((tag >> 8) & 0xff));
+            fontNeeded.Append(char16_t(tag & 0xff));
+        }
+        mMissingFonts[i] = 0;
+    }
+    if (!fontNeeded.IsEmpty()) {
+        nsCOMPtr<nsIObserverService> service = GetObserverService();
+        service->NotifyObservers(nullptr, "font-needed", fontNeeded.get());
+    }
+}
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -11,27 +11,29 @@
 #include "gfxPoint.h"
 #include "gfxFont.h"
 #include "nsTArray.h"
 #include "gfxSkipChars.h"
 #include "gfxPlatform.h"
 #include "mozilla/MemoryReporting.h"
 #include "DrawMode.h"
 #include "harfbuzz/hb.h"
+#include "nsUnicodeScriptCodes.h"
 
 #ifdef DEBUG
 #include <stdio.h>
 #endif
 
 class gfxContext;
 class gfxFontGroup;
 class gfxUserFontSet;
 class gfxTextContextPaint;
 class nsIAtom;
 class nsILanguageAtomService;
+class gfxMissingFontRecorder;
 
 /**
  * Callback for Draw() to use when drawing text with mode
  * DrawMode::GLYPH_PATH.
  */
 struct gfxTextRunDrawCallbacks {
 
     /**
@@ -764,40 +766,43 @@ public:
 
     /**
      * Make a textrun for a given string.
      * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the
      * textrun will copy it.
      * This calls FetchGlyphExtents on the textrun.
      */
     virtual gfxTextRun *MakeTextRun(const char16_t *aString, uint32_t aLength,
-                                    const Parameters *aParams, uint32_t aFlags);
+                                    const Parameters *aParams, uint32_t aFlags,
+                                    gfxMissingFontRecorder *aMFR);
     /**
      * Make a textrun for a given string.
      * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the
      * textrun will copy it.
      * This calls FetchGlyphExtents on the textrun.
      */
     virtual gfxTextRun *MakeTextRun(const uint8_t *aString, uint32_t aLength,
-                                    const Parameters *aParams, uint32_t aFlags);
+                                    const Parameters *aParams, uint32_t aFlags,
+                                    gfxMissingFontRecorder *aMFR);
 
     /**
      * Textrun creation helper for clients that don't want to pass
      * a full Parameters record.
      */
     template<typename T>
     gfxTextRun *MakeTextRun(const T *aString, uint32_t aLength,
                             gfxContext *aRefContext,
                             int32_t aAppUnitsPerDevUnit,
-                            uint32_t aFlags)
+                            uint32_t aFlags,
+                            gfxMissingFontRecorder *aMFR)
     {
         gfxTextRunFactory::Parameters params = {
             aRefContext, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevUnit
         };
-        return MakeTextRun(aString, aLength, &params, aFlags);
+        return MakeTextRun(aString, aLength, &params, aFlags, aMFR);
     }
 
     /**
      * Get the (possibly-cached) width of the hyphen character.
      * The aCtx and aAppUnitsPerDevUnit parameters will be used only if
      * needed to initialize the cached hyphen width; otherwise they are
      * ignored.
      */
@@ -1083,27 +1088,29 @@ protected:
     void InitMetricsForBadFont(gfxFont* aBadFont);
 
     // Set up the textrun glyphs for an entire text run:
     // find script runs, and then call InitScriptRun for each
     template<typename T>
     void InitTextRun(gfxContext *aContext,
                      gfxTextRun *aTextRun,
                      const T *aString,
-                     uint32_t aLength);
+                     uint32_t aLength,
+                     gfxMissingFontRecorder *aMFR);
 
     // InitTextRun helper to handle a single script run, by finding font ranges
     // and calling each font's InitTextRun() as appropriate
     template<typename T>
     void InitScriptRun(gfxContext *aContext,
                        gfxTextRun *aTextRun,
                        const T *aString,
                        uint32_t aScriptRunStart,
                        uint32_t aScriptRunEnd,
-                       int32_t aRunScript);
+                       int32_t aRunScript,
+                       gfxMissingFontRecorder *aMFR);
 
     // Helper for font-matching:
     // When matching the italic case, allow use of the regular face
     // if it supports a character but the italic one doesn't.
     // Return null if regular face doesn't support aCh
     already_AddRefed<gfxFont>
     FindNonItalicFaceForChar(gfxFontFamily* aFamily, uint32_t aCh);
 
@@ -1119,9 +1126,61 @@ protected:
 
     // lookup and add a font with a given name (i.e. *not* a generic!)
     virtual void FindPlatformFont(const nsAString& aName,
                                   bool aUseFontSet,
                                   void *aClosure);
 
     static nsILanguageAtomService* gLangService;
 };
+
+// A "missing font recorder" is to be used during text-run creation to keep
+// a record of any scripts encountered for which font coverage was lacking;
+// when Flush() is called, it sends a notification that front-end code can use
+// to download fonts on demand (or whatever else it wants to do).
+
+#define GFX_MISSING_FONTS_NOTIFY_PREF "gfx.missing_fonts.notify"
+
+class gfxMissingFontRecorder {
+public:
+    gfxMissingFontRecorder()
+    {
+        MOZ_COUNT_CTOR(gfxMissingFontRecorder);
+        memset(&mMissingFonts, 0, sizeof(mMissingFonts));
+    }
+
+    ~gfxMissingFontRecorder()
+    {
+#ifdef DEBUG
+        for (uint32_t i = 0; i < kNumScriptBitsWords; i++) {
+            NS_ASSERTION(mMissingFonts[i] == 0,
+                         "failed to flush the missing-font recorder");
+        }
 #endif
+        MOZ_COUNT_DTOR(gfxMissingFontRecorder);
+    }
+
+    // record this script code in our mMissingFonts bitset
+    void RecordScript(int32_t aScriptCode)
+    {
+        mMissingFonts[uint32_t(aScriptCode) >> 5] |=
+            (1 << (uint32_t(aScriptCode) & 0x1f));
+    }
+
+    // send a notification of any missing-scripts that have been
+    // recorded, and clear the mMissingFonts set for re-use
+    void Flush();
+
+    // forget any missing-scripts that have been recorded up to now;
+    // called before discarding a recorder we no longer care about
+    void Clear()
+    {
+        memset(&mMissingFonts, 0, sizeof(mMissingFonts));
+    }
+
+private:
+    // Number of 32-bit words needed for the missing-script flags
+    static const uint32_t kNumScriptBitsWords =
+        ((MOZ_NUM_SCRIPT_CODES + 31) / 32);
+    uint32_t mMissingFonts[kNumScriptBitsWords];
+};
+
+#endif
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -59,16 +59,17 @@
 #include "nsIDOMEvent.h"
 #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"
 
 // Needed for Start/Stop of Image Animation
 #include "imgIContainer.h"
 #include "nsIImageLoadingContent.h"
 
 #include "nsCSSParser.h"
 #include "nsBidiUtils.h"
 #include "nsServiceManagerUtils.h"
@@ -244,16 +245,20 @@ nsPresContext::nsPresContext(nsIDocument
 
   mCounterStylesDirty = true;
 
   // if text perf logging enabled, init stats struct
   PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textperf);
   if (log && log->level >= PR_LOG_WARNING) {
     mTextPerf = new gfxTextPerfMetrics();
   }
+
+  if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
+    mMissingFonts = new gfxMissingFontRecorder();
+  }
 }
 
 void
 nsPresContext::Destroy()
 {
   if (mEventManager) {
     // unclear if these are needed, but can't hurt
     mEventManager->NotifyDestroyPresContext(this);
@@ -336,16 +341,19 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPresCo
 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsPresContext, LastRelease())
 
 void
 nsPresContext::LastRelease()
 {
   if (IsRoot()) {
     static_cast<nsRootPresContext*>(this)->CancelDidPaintTimer();
   }
+  if (mMissingFonts) {
+    mMissingFonts->Clear();
+  }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsPresContext)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsPresContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument);
   // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDeviceContext); // not xpcom
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventManager);
@@ -868,16 +876,30 @@ nsPresContext::PreferenceChanged(const c
       nscoord width = NSToCoordRound(oldWidthDevPixels*AppUnitsPerDevPixel());
       nscoord height = NSToCoordRound(oldHeightDevPixels*AppUnitsPerDevPixel());
       vm->SetWindowDimensions(width, height);
 
       AppUnitsPerDevPixelChanged();
     }
     return;
   }
+  if (prefName.EqualsLiteral(GFX_MISSING_FONTS_NOTIFY_PREF)) {
+    if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
+      if (!mMissingFonts) {
+        mMissingFonts = new gfxMissingFontRecorder();
+        // trigger reflow to detect missing fonts on the current page
+        mPrefChangePendingNeedsReflow = true;
+      }
+    } else {
+      if (mMissingFonts) {
+        mMissingFonts->Clear();
+      }
+      mMissingFonts = nullptr;
+    }
+  }
   if (StringBeginsWith(prefName, NS_LITERAL_CSTRING("font."))) {
     // Changes to font family preferences don't change anything in the
     // computed style data, so the style system won't generate a reflow
     // hint for us.  We need to do that manually.
 
     // FIXME We could probably also handle changes to
     // browser.display.auto_quality_min_font_size here, but that
     // probably also requires clearing the text run cache, so don't
@@ -2190,16 +2212,24 @@ nsPresContext::RebuildCounterStyles()
       NS_NewRunnableMethod(this, &nsPresContext::HandleRebuildCounterStyles);
     if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
       mPostedFlushCounterStyles = true;
     }
   }
 }
 
 void
+nsPresContext::NotifyMissingFonts()
+{
+  if (mMissingFonts) {
+    mMissingFonts->Flush();
+  }
+}
+
+void
 nsPresContext::EnsureSafeToHandOutCSSRules()
 {
   CSSStyleSheet::EnsureUniqueInnerResult res =
     mShell->StyleSet()->EnsureUniqueInnerOnCSSSheets();
   if (res == CSSStyleSheet::eUniqueInner_AlreadyUnique) {
     // Nothing to do.
     return;
   }
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -60,16 +60,17 @@ class gfxUserFontSet;
 class gfxTextPerfMetrics;
 struct nsFontFaceRuleContainer;
 class nsPluginFrame;
 class nsTransitionManager;
 class nsAnimationManager;
 class nsRefreshDriver;
 class nsIWidget;
 class nsDeviceContext;
+class gfxMissingFontRecorder;
 
 namespace mozilla {
 class EventStateManager;
 class RestyleManager;
 class CounterStyleManager;
 namespace dom {
 class FontFaceSet;
 }
@@ -871,16 +872,19 @@ public:
   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();
 
+  gfxMissingFontRecorder *MissingFontRecorder() { return mMissingFonts; }
+  void NotifyMissingFonts();
+
   mozilla::dom::FontFaceSet* Fonts();
 
   void FlushCounterStyles();
   void RebuildCounterStyles(); // asynchronously
 
   // Ensure that it is safe to hand out CSS rules outside the layout
   // engine by ensuring that all CSS style sheets have unique inners
   // and, if necessary, synchronously rebuilding all style data.
@@ -1246,16 +1250,18 @@ protected:
   nsInvalidateRequestList mUndeliveredInvalidateRequestsBeforeLastPaint;
 
   // container for per-context fonts (downloadable, SVG, etc.)
   nsRefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
 
   // text performance metrics
   nsAutoPtr<gfxTextPerfMetrics>   mTextPerf;
 
+  nsAutoPtr<gfxMissingFontRecorder> mMissingFonts;
+
   nsRect                mVisibleArea;
   nsSize                mPageSize;
   float                 mPageScale;
   float                 mPPScale;
 
   nscolor               mDefaultColor;
   nscolor               mBackgroundColor;
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -9114,16 +9114,18 @@ PresShell::DidDoReflow(bool aInterruptib
   if (sSynthMouseMove) {
     SynthesizeMouseMove(false);
   }
 
   if (mTouchCaret) {
     mTouchCaret->UpdatePositionIfNeeded();
   }
 
+  mPresContext->NotifyMissingFonts();
+
   if (!aWasInterrupted) {
     ClearReflowOnZoomPending();
   }
 }
 
 DOMHighResTimeStamp
 PresShell::GetPerformanceNow()
 {
--- a/layout/generic/MathMLTextRunFactory.cpp
+++ b/layout/generic/MathMLTextRunFactory.cpp
@@ -524,17 +524,18 @@ MathVariant(uint32_t aCh, uint8_t aMathV
 
 }
 
 #define TT_SSTY TRUETYPE_TAG('s', 's', 't', 'y')
 #define TT_DTLS TRUETYPE_TAG('d', 't', 'l', 's')
 
 void
 MathMLTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
-                                     gfxContext* aRefContext)
+                                     gfxContext* aRefContext,
+                                     gfxMissingFontRecorder* aMFR)
 {
   gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
 
   nsAutoString convertedString;
   nsAutoTArray<bool,50> charsToMergeArray;
   nsAutoTArray<bool,50> deletedCharsArray;
   nsAutoTArray<nsStyleContext*,50> styleArray;
   nsAutoTArray<uint8_t,50> canBreakBeforeArray;
@@ -761,29 +762,29 @@ MathMLTextRunFactory::RebuildTextRun(nsT
   if (mInnerTransformingTextRunFactory) {
     transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
         convertedString.BeginReading(), convertedString.Length(),
         &innerParams, newFontGroup, flags, styleArray.Elements(), false);
     child = transformedChild.get();
   } else {
     cachedChild = newFontGroup->MakeTextRun(
         convertedString.BeginReading(), convertedString.Length(),
-        &innerParams, flags);
+        &innerParams, flags, aMFR);
     child = cachedChild.get();
   }
   if (!child)
     return;
   // Copy potential linebreaks into child so they're preserved
   // (and also child will be shaped appropriately)
   NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
                "Dropped characters or break-before values somewhere!");
   child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(),
       canBreakBeforeArray.Elements(), aRefContext);
   if (transformedChild) {
-    transformedChild->FinishSettingProperties(aRefContext);
+    transformedChild->FinishSettingProperties(aRefContext, aMFR);
   }
 
   if (mergeNeeded) {
     // Now merge multiple characters into one multi-glyph character as required
     NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
                  "source length mismatch");
     NS_ASSERTION(deletedCharsArray.Length() == aTextRun->GetLength(),
                  "destination length mismatch");
--- a/layout/generic/MathMLTextRunFactory.h
+++ b/layout/generic/MathMLTextRunFactory.h
@@ -17,17 +17,18 @@ public:
                        uint32_t aFlags, uint8_t aSSTYScriptLevel,
                        float aFontInflation)
     : mInnerTransformingTextRunFactory(aInnerTransformingTextRunFactory),
       mFlags(aFlags),
       mFontInflation(aFontInflation),
       mSSTYScriptLevel(aSSTYScriptLevel) {}
 
   virtual void RebuildTextRun(nsTransformedTextRun* aTextRun,
-                              gfxContext* aRefContext) MOZ_OVERRIDE;
+                              gfxContext* aRefContext,
+                              gfxMissingFontRecorder* aMFR) MOZ_OVERRIDE;
   enum {
     // Style effects which may override single character <mi> behaviour
     MATH_FONT_STYLING_NORMAL   = 0x1, // fontstyle="normal" has been set.
     MATH_FONT_WEIGHT_BOLD      = 0x2, // fontweight="bold" has been set.
     MATH_FONT_FEATURE_DTLS     = 0x4, // font feature dtls should be set
   };
 
 protected:
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -556,20 +556,21 @@ public:
 };
 
 // Helper to create a textrun and remember it in the textframe cache,
 // for either 8-bit or 16-bit text strings
 template<typename T>
 gfxTextRun *
 MakeTextRun(const T *aText, uint32_t aLength,
             gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams,
-            uint32_t aFlags)
+            uint32_t aFlags, gfxMissingFontRecorder *aMFR)
 {
     nsAutoPtr<gfxTextRun> textRun(aFontGroup->MakeTextRun(aText, aLength,
-                                                          aParams, aFlags));
+                                                          aParams, aFlags,
+                                                          aMFR));
     if (!textRun) {
         return nullptr;
     }
     nsresult rv = gTextRuns->AddObject(textRun);
     if (NS_FAILED(rv)) {
         gTextRuns->RemoveFromCache(textRun);
         return nullptr;
     }
@@ -837,16 +838,17 @@ CreateObserversForAnimatedGlyphs(gfxText
  */
 class BuildTextRunsScanner {
 public:
   BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext,
       nsIFrame* aLineContainer, nsTextFrame::TextRunType aWhichTextRun) :
     mCurrentFramesAllSameTextRun(nullptr),
     mContext(aContext),
     mLineContainer(aLineContainer),
+    mMissingFonts(aPresContext->MissingFontRecorder()),
     mBidiEnabled(aPresContext->BidiEnabled()),
     mSkipIncompleteTextRuns(false),
     mWhichTextRun(aWhichTextRun),
     mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
     mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
     ResetRunInfo();
   }
   ~BuildTextRunsScanner() {
@@ -966,25 +968,25 @@ public:
       NS_ASSERTION(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED,
                    "Text run should be transformed!");
       nsTransformedTextRun* transformedTextRun =
         static_cast<nsTransformedTextRun*>(mTextRun);
       transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength,
                                             aCapitalize, mContext);
     }
 
-    void Finish() {
+    void Finish(gfxMissingFontRecorder* aMFR) {
       NS_ASSERTION(!(mTextRun->GetFlags() &
                      (gfxTextRunFactory::TEXT_UNUSED_FLAGS |
                       nsTextFrameUtils::TEXT_UNUSED_FLAG)),
                    "Flag set that should never be set! (memory safety error?)");
       if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) {
         nsTransformedTextRun* transformedTextRun =
           static_cast<nsTransformedTextRun*>(mTextRun);
-        transformedTextRun->FinishSettingProperties(mContext);
+        transformedTextRun->FinishSettingProperties(mContext, aMFR);
       }
       // The way nsTransformedTextRun is implemented, its glyph runs aren't
       // available until after nsTransformedTextRun::FinishSettingProperties()
       // is called. So that's why we defer checking for animated glyphs to here.
       CreateObserversForAnimatedGlyphs(mTextRun);
     }
 
     gfxTextRun*  mTextRun;
@@ -1002,16 +1004,17 @@ private:
   nsLineBreaker                 mLineBreaker;
   gfxTextRun*                   mCurrentFramesAllSameTextRun;
   gfxContext*                   mContext;
   nsIFrame*                     mLineContainer;
   nsTextFrame*                  mLastFrame;
   // The common ancestor of the current frame and the previous leaf frame
   // on the line, or null if there was no previous leaf frame.
   nsIFrame*                     mCommonAncestorWithLastFrame;
+  gfxMissingFontRecorder*       mMissingFonts;
   // mMaxTextLength is an upper bound on the size of the text in all mapped frames
   // The value UINT32_MAX represents overflow; text will be discarded
   uint32_t                      mMaxTextLength;
   bool                          mDoubleByteText;
   bool                          mBidiEnabled;
   bool                          mStartOfLine;
   bool                          mSkipIncompleteTextRuns;
   bool                          mCanStopOnThisLine;
@@ -1501,17 +1504,17 @@ void BuildTextRunsScanner::FlushLineBrea
     aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK);
   }
 
   for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
     if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) {
       // TODO cause frames associated with the textrun to be reflowed, if they
       // aren't being reflowed already!
     }
-    mBreakSinks[i]->Finish();
+    mBreakSinks[i]->Finish(mMissingFonts);
   }
   mBreakSinks.Clear();
 
   for (uint32_t i = 0; i < mTextRunsToDelete.Length(); ++i) {
     gfxTextRun* deleteTextRun = mTextRunsToDelete[i];
     gTextRuns->RemoveFromCache(deleteTextRun);
     delete deleteTextRun;
   }
@@ -2133,37 +2136,41 @@ BuildTextRunsScanner::BuildTextRunForFra
       { mContext, finalUserData, &skipChars,
         textBreakPointsAfterTransform.Elements(),
         uint32_t(textBreakPointsAfterTransform.Length()),
         int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
 
   if (mDoubleByteText) {
     const char16_t* text = static_cast<const char16_t*>(textPtr);
     if (transformingFactory) {
-      textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
-                                                 fontGroup, textFlags, styles.Elements());
+      textRun = transformingFactory->MakeTextRun(text, transformedLength,
+                                                 &params, fontGroup, textFlags,
+                                                 styles.Elements(), true);
       if (textRun) {
         // ownership of the factory has passed to the textrun
         transformingFactory.forget();
       }
     } else {
-      textRun = MakeTextRun(text, transformedLength, fontGroup, &params, textFlags);
+      textRun = MakeTextRun(text, transformedLength, fontGroup, &params,
+                            textFlags, mMissingFonts);
     }
   } else {
     const uint8_t* text = static_cast<const uint8_t*>(textPtr);
     textFlags |= gfxFontGroup::TEXT_IS_8BIT;
     if (transformingFactory) {
-      textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
-                                                 fontGroup, textFlags, styles.Elements());
+      textRun = transformingFactory->MakeTextRun(text, transformedLength,
+                                                 &params, fontGroup, textFlags,
+                                                 styles.Elements(), true);
       if (textRun) {
         // ownership of the factory has passed to the textrun
         transformingFactory.forget();
       }
     } else {
-      textRun = MakeTextRun(text, transformedLength, fontGroup, &params, textFlags);
+      textRun = MakeTextRun(text, transformedLength, fontGroup, &params,
+                            textFlags, mMissingFonts);
     }
   }
   if (!textRun) {
     DestroyUserData(userDataToDestroy);
     return nullptr;
   }
 
   // We have to set these up after we've created the textrun, because
--- a/layout/generic/nsTextRunTransformations.cpp
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -591,17 +591,17 @@ nsCaseTransformTextRunFactory::Transform
     }
   }
 
   return mergeNeeded;
 }
 
 void
 nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
-    gfxContext* aRefContext)
+    gfxContext* aRefContext, gfxMissingFontRecorder *aMFR)
 {
   nsAutoString convertedString;
   nsAutoTArray<bool,50> charsToMergeArray;
   nsAutoTArray<bool,50> deletedCharsArray;
   nsAutoTArray<uint8_t,50> canBreakBeforeArray;
   nsAutoTArray<nsStyleContext*,50> styleArray;
 
   bool mergeNeeded = TransformString(aTextRun->mString,
@@ -626,29 +626,29 @@ nsCaseTransformTextRunFactory::RebuildTe
   if (mInnerTransformingTextRunFactory) {
     transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
         convertedString.BeginReading(), convertedString.Length(),
         &innerParams, fontGroup, flags, styleArray.Elements(), false);
     child = transformedChild.get();
   } else {
     cachedChild = fontGroup->MakeTextRun(
         convertedString.BeginReading(), convertedString.Length(),
-        &innerParams, flags);
+        &innerParams, flags, aMFR);
     child = cachedChild.get();
   }
   if (!child)
     return;
   // Copy potential linebreaks into child so they're preserved
   // (and also child will be shaped appropriately)
   NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
                "Dropped characters or break-before values somewhere!");
   child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(),
       canBreakBeforeArray.Elements(), aRefContext);
   if (transformedChild) {
-    transformedChild->FinishSettingProperties(aRefContext);
+    transformedChild->FinishSettingProperties(aRefContext, aMFR);
   }
 
   if (mergeNeeded) {
     // Now merge multiple characters into one multi-glyph character as required
     // and deal with skipping deleted accent chars
     NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
                  "source length mismatch");
     NS_ASSERTION(deletedCharsArray.Length() == aTextRun->GetLength(),
--- a/layout/generic/nsTextRunTransformations.h
+++ b/layout/generic/nsTextRunTransformations.h
@@ -16,23 +16,27 @@ class nsTransformedTextRun;
 class nsTransformingTextRunFactory {
 public:
   virtual ~nsTransformingTextRunFactory() {}
 
   // Default 8-bit path just transforms to Unicode and takes that path
   nsTransformedTextRun* MakeTextRun(const uint8_t* aString, uint32_t aLength,
                                     const gfxFontGroup::Parameters* aParams,
                                     gfxFontGroup* aFontGroup, uint32_t aFlags,
-                                    nsStyleContext** aStyles, bool aOwnsFactory = true);
+                                    nsStyleContext** aStyles,
+                                    bool aOwnsFactory);
   nsTransformedTextRun* MakeTextRun(const char16_t* aString, uint32_t aLength,
                                     const gfxFontGroup::Parameters* aParams,
                                     gfxFontGroup* aFontGroup, uint32_t aFlags,
-                                    nsStyleContext** aStyles, bool aOwnsFactory = true);
+                                    nsStyleContext** aStyles,
+                                    bool aOwnsFactory);
 
-  virtual void RebuildTextRun(nsTransformedTextRun* aTextRun, gfxContext* aRefContext) = 0;
+  virtual void RebuildTextRun(nsTransformedTextRun* aTextRun,
+                              gfxContext* aRefContext,
+                              gfxMissingFontRecorder* aMFR) = 0;
 };
 
 /**
  * Builds textruns that transform the text in some way (e.g., capitalize)
  * and then render the text using some other textrun implementation.
  */
 class nsCaseTransformTextRunFactory : public nsTransformingTextRunFactory {
 public:
@@ -43,17 +47,19 @@ public:
   // via the fontgroup.
   
   // Takes ownership of aInnerTransformTextRunFactory
   explicit nsCaseTransformTextRunFactory(nsTransformingTextRunFactory* aInnerTransformingTextRunFactory,
                                          bool aAllUppercase = false)
     : mInnerTransformingTextRunFactory(aInnerTransformingTextRunFactory),
       mAllUppercase(aAllUppercase) {}
 
-  virtual void RebuildTextRun(nsTransformedTextRun* aTextRun, gfxContext* aRefContext) MOZ_OVERRIDE;
+  virtual void RebuildTextRun(nsTransformedTextRun* aTextRun,
+                              gfxContext* aRefContext,
+                              gfxMissingFontRecorder* aMFR) MOZ_OVERRIDE;
 
   // Perform a transformation on the given string, writing the result into
   // aConvertedString. If aAllUppercase is true, the transform is (global)
   // upper-casing, and aLanguage is used to determine any language-specific
   // behavior; otherwise, an nsTransformedTextRun should be passed in
   // as aTextRun and its styles will be used to determine the transform(s)
   // to be applied.
   // If such an input textrun is provided, then its line-breaks and styles
@@ -100,21 +106,22 @@ public:
   virtual bool SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength,
                                         uint8_t* aBreakBefore,
                                         gfxContext* aRefContext);
   /**
    * Called after SetCapitalization and SetPotentialLineBreaks
    * are done and before we request any data from the textrun. Also always
    * called after a Create.
    */
-  void FinishSettingProperties(gfxContext* aRefContext)
+  void FinishSettingProperties(gfxContext* aRefContext,
+                               gfxMissingFontRecorder* aMFR)
   {
     if (mNeedsRebuild) {
       mNeedsRebuild = false;
-      mFactory->RebuildTextRun(this, aRefContext);
+      mFactory->RebuildTextRun(this, aRefContext, aMFR);
     }
   }
 
   // override the gfxTextRun impls to account for additional members here
   virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) MOZ_MUST_OVERRIDE;
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) MOZ_MUST_OVERRIDE;
 
   nsTransformingTextRunFactory       *mFactory;
--- a/layout/mathml/nsMathMLChar.cpp
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -384,17 +384,17 @@ nsPropertiesTable::MakeTextRun(gfxContex
                                int32_t            aAppUnitsPerDevPixel,
                                gfxFontGroup*      aFontGroup,
                                const nsGlyphCode& aGlyph)
 {
   NS_ASSERTION(!aGlyph.IsGlyphID(),
                "nsPropertiesTable can only access glyphs by code point");
   return aFontGroup->
     MakeTextRun(aGlyph.code, aGlyph.Length(), aThebesContext,
-                aAppUnitsPerDevPixel, 0);
+                aAppUnitsPerDevPixel, 0, nullptr);
 }
 
 // An instance of nsOpenTypeTable is associated with one gfxFontEntry that
 // corresponds to an Open Type font with a MATH table. All the glyphs come from
 // the same font and the calls to access size variants and parts are directly
 // forwarded to the gfx code.
 class nsOpenTypeTable MOZ_FINAL : public nsGlyphTable {
 public:
@@ -465,17 +465,17 @@ void
 nsOpenTypeTable::UpdateCache(gfxContext*   aThebesContext,
                              int32_t       aAppUnitsPerDevPixel,
                              gfxFontGroup* aFontGroup,
                              char16_t      aChar)
 {
   if (mCharCache != aChar) {
     nsAutoPtr<gfxTextRun> textRun;
     textRun = aFontGroup->
-      MakeTextRun(&aChar, 1, aThebesContext, aAppUnitsPerDevPixel, 0);
+      MakeTextRun(&aChar, 1, aThebesContext, aAppUnitsPerDevPixel, 0, nullptr);
     const gfxTextRun::CompressedGlyph& data = textRun->GetCharacterGlyphs()[0];
     if (data.IsSimpleGlyph()) {
       mGlyphID = data.GetSimpleGlyph();
     } else if (data.GetGlyphCount() == 1) {
       mGlyphID = textRun->GetDetailedGlyphs(0)->mGlyphID;
     } else {
       mGlyphID = 0;
     }
@@ -1548,17 +1548,18 @@ nsMathMLChar::StretchInternal(nsPresCont
                   gfxFont::eHorizontal,
                   aPresContext->GetUserFontSet(),
                   aPresContext->GetTextPerfMetrics(),
                   *getter_AddRefs(fm));
   uint32_t len = uint32_t(mData.Length());
   nsAutoPtr<gfxTextRun> textRun;
   textRun = fm->GetThebesFontGroup()->
     MakeTextRun(static_cast<const char16_t*>(mData.get()), len, aThebesContext,
-                aPresContext->AppUnitsPerDevPixel(), 0);
+                aPresContext->AppUnitsPerDevPixel(), 0,
+                aPresContext->MissingFontRecorder());
   aDesiredStretchSize = MeasureTextRun(aThebesContext, textRun);
   mGlyphs[0] = textRun;
 
   bool maxWidth = (NS_STRETCH_MAXWIDTH & aStretchHint) != 0;
   if (!maxWidth) {
     mUnscaledAscent = aDesiredStretchSize.ascent;
   }
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -587,16 +587,20 @@ pref("gfx.downloadable_fonts.woff2.enabl
 pref("gfx.downloadable_fonts.woff2.enabled", true);
 #endif
 
 #ifdef ANDROID
 pref("gfx.bundled_fonts.enabled", true);
 pref("gfx.bundled_fonts.force-enabled", false);
 #endif
 
+// Do we fire a notification about missing fonts, so the front-end can decide
+// whether to try and do something about it (e.g. download additional fonts)?
+pref("gfx.missing_fonts.notify", false);
+
 pref("gfx.filter.nearest.force-enabled", false);
 
 // prefs controlling the font (name/cmap) loader that runs shortly after startup
 pref("gfx.font_loader.families_per_slice", 3); // read in info 3 families at a time
 #ifdef XP_WIN
 pref("gfx.font_loader.delay", 120000);         // 2 minutes after startup
 pref("gfx.font_loader.interval", 1000);        // every 1 second until complete
 #else