Merge mozilla-central and inbound
authorEd Morley <emorley@mozilla.com>
Wed, 29 Jan 2014 10:39:41 +0000
changeset 181742 0fd024e3ef803e2d42a04db762f5acb0f6d0f2c2
parent 181727 b28e216c8d07f8e383a24cac46a18bbd279a995b (current diff)
parent 181741 8f535f3864ad93c537fd14fd166465593b7361b3 (diff)
child 181743 f8463499fb57a908d0d50b020ec0c5d5c1359920
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.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
Merge mozilla-central and inbound
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -1038,19 +1038,18 @@ IDBObjectStore::AppendIndexUpdateInfo(
     return NS_OK;
   }
 
   JS::Rooted<JS::Value> val(aCx);
   if (NS_FAILED(aKeyPath.ExtractKeyAsJSVal(aCx, aVal, val.address()))) {
     return NS_OK;
   }
 
-  if (!JSVAL_IS_PRIMITIVE(val) &&
-      JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(val))) {
-    JS::Rooted<JSObject*> array(aCx, JSVAL_TO_OBJECT(val));
+  if (JS_IsArrayObject(aCx, val)) {
+    JS::Rooted<JSObject*> array(aCx, &val.toObject());
     uint32_t arrayLength;
     if (!JS_GetArrayLength(aCx, array, &arrayLength)) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
     for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
       JS::Rooted<JS::Value> arrayItem(aCx);
--- a/dom/indexedDB/KeyPath.cpp
+++ b/dom/indexedDB/KeyPath.cpp
@@ -265,18 +265,17 @@ nsresult
 KeyPath::Parse(JSContext* aCx, const JS::Value& aValue_, KeyPath* aKeyPath)
 {
   JS::Rooted<JS::Value> aValue(aCx, aValue_);
   KeyPath keyPath(0);
 
   aKeyPath->SetType(NONEXISTENT);
 
   // See if this is a JS array.
-  if (!JSVAL_IS_PRIMITIVE(aValue) &&
-      JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(aValue))) {
+  if (JS_IsArrayObject(aCx, aValue)) {
 
     JS::Rooted<JSObject*> obj(aCx, JSVAL_TO_OBJECT(aValue));
 
     uint32_t length;
     if (!JS_GetArrayLength(aCx, obj, &length)) {
       return NS_ERROR_FAILURE;
     }
 
--- a/dom/mobilemessage/src/MobileMessageManager.cpp
+++ b/dom/mobilemessage/src/MobileMessageManager.cpp
@@ -165,18 +165,17 @@ MobileMessageManager::Send(JSContext* aC
 NS_IMETHODIMP
 MobileMessageManager::Send(JS::Handle<JS::Value> aNumber,
                            const nsAString& aMessage,
                            JS::Handle<JS::Value> aSendParams,
                            JSContext* aCx,
                            uint8_t aArgc,
                            JS::MutableHandle<JS::Value> aReturn)
 {
-  if (!aNumber.isString() &&
-      !(aNumber.isObject() && JS_IsArrayObject(aCx, &aNumber.toObject()))) {
+  if (!aNumber.isString() && !JS_IsArrayObject(aCx, aNumber)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsresult rv;
   nsIScriptContext* sc = GetContextForEventHandlers(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_STATE(sc);
 
@@ -335,17 +334,17 @@ MobileMessageManager::Delete(JS::Handle<
   uint32_t size;
 
   if (aParam.isInt32()) {
     // Single Integer Message ID
     id = aParam.toInt32();
 
     size = 1;
     idArray = &id;
-  } else if (!JS_IsArrayObject(aCx, &aParam.toObject())) {
+  } else if (!JS_IsArrayObject(aCx, aParam)) {
     // Single SmsMessage/MmsMessage object
     rv = GetMessageId(aCx, aParam, &id);
     NS_ENSURE_SUCCESS(rv, rv);
 
     size = 1;
     idArray = &id;
   } else {
     // Int32[], SmsMessage[], or MmsMessage[]
--- a/dom/workers/File.cpp
+++ b/dom/workers/File.cpp
@@ -32,19 +32,19 @@ class Blob
   ~Blob();
 
   static const JSClass sClass;
   static const JSPropertySpec sProperties[];
   static const JSFunctionSpec sFunctions[];
 
 public:
   static JSObject*
-  InitClass(JSContext* aCx, JSObject* aObj)
+  InitClass(JSContext* aCx, JS::Handle<JSObject*> aObj)
   {
-    return JS_InitClass(aCx, aObj, nullptr, &sClass, Construct, 0,
+    return JS_InitClass(aCx, aObj, JS::NullPtr(), &sClass, Construct, 0,
                         sProperties, sFunctions, nullptr, nullptr);
   }
 
   static JSObject*
   Create(JSContext* aCx, nsIDOMBlob* aBlob)
   {
     JS_ASSERT(SameCOMIdentity(static_cast<nsISupports*>(aBlob), aBlob));
 
@@ -232,17 +232,17 @@ class File : public Blob
   File();
   ~File();
 
   static const JSClass sClass;
   static const JSPropertySpec sProperties[];
 
 public:
   static JSObject*
-  InitClass(JSContext* aCx, JSObject* aObj, JSObject* aParentProto)
+  InitClass(JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<JSObject*> aParentProto)
   {
     return JS_InitClass(aCx, aObj, aParentProto, &sClass, Construct, 0,
                         sProperties, nullptr, nullptr, nullptr);
   }
 
   static JSObject*
   Create(JSContext* aCx, nsIDOMFile* aFile)
   {
@@ -465,17 +465,17 @@ JSObject*
 CreateBlob(JSContext* aCx, nsIDOMBlob* aBlob)
 {
   return Blob::Create(aCx, aBlob);
 }
 
 bool
 InitClasses(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
 {
-  JSObject* blobProto = Blob::InitClass(aCx, aGlobal);
+  JS::Rooted<JSObject*> blobProto(aCx, Blob::InitClass(aCx, aGlobal));
   return blobProto && File::InitClass(aCx, aGlobal, blobProto);
 }
 
 nsIDOMBlob*
 GetDOMBlobFromJSObject(JSObject* aObj)
 {
   return Blob::GetPrivate(aObj);
 }
--- a/gfx/thebes/gfxDWriteFontList.cpp
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -142,28 +142,31 @@ GetDirectWriteFaceName(IDWriteFont *aFon
         return hr;
     }
 
     aFontName.Assign(faceName.Elements());
     return S_OK;
 }
 
 void
-gfxDWriteFontFamily::FindStyleVariations()
+gfxDWriteFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
 {
     HRESULT hr;
     if (mHasStyles) {
         return;
     }
     mHasStyles = true;
 
     gfxPlatformFontList *fp = gfxPlatformFontList::PlatformFontList();
 
     bool skipFaceNames = mFaceNamesInitialized ||
                          !fp->NeedFullnamePostscriptNames();
+    bool fontInfoShouldHaveFaceNames = !mFaceNamesInitialized &&
+                                       fp->NeedFullnamePostscriptNames() &&
+                                       aFontInfoData;
 
     for (UINT32 i = 0; i < mDWFamily->GetFontCount(); i++) {
         nsRefPtr<IDWriteFont> font;
         hr = mDWFamily->GetFont(i, getter_AddRefs(font));
         if (FAILED(hr)) {
             // This should never happen.
             NS_WARNING("Failed to get existing font from family.");
             continue;
@@ -185,17 +188,25 @@ gfxDWriteFontFamily::FindStyleVariations
         fullID.Append(faceName);
 
         gfxDWriteFontEntry *fe = new gfxDWriteFontEntry(fullID, font);
         fe->SetForceGDIClassic(mForceGDIClassic);
         AddFontEntry(fe);
 
         // postscript/fullname if needed
         nsAutoString psname, fullname;
-        if (!skipFaceNames) {
+        if (fontInfoShouldHaveFaceNames) {
+            aFontInfoData->GetFaceNames(fe->Name(), fullname, psname);
+            if (!fullname.IsEmpty()) {
+                fp->AddFullname(fe, fullname);
+            }
+            if (!psname.IsEmpty()) {
+                fp->AddPostscriptName(fe, psname);
+            }
+        } else if (!skipFaceNames) {
             hr = GetDirectWriteFaceName(font, PSNAME_ID, psname);
             if (FAILED(hr)) {
                 skipFaceNames = true;
             } else if (psname.Length() > 0) {
                 fp->AddPostscriptName(fe, psname);
             }
 
             hr = GetDirectWriteFaceName(font, FULLNAME_ID, fullname);
@@ -491,40 +502,48 @@ gfxDWriteFontEntry::GetFontTable(uint32_
                               HB_MEMORY_MODE_READONLY,
                               ftr, DestroyBlobFunc);
     }
 
     return nullptr;
 }
 
 nsresult
-gfxDWriteFontEntry::ReadCMAP()
+gfxDWriteFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
 {
-    nsresult rv;
-
     // attempt this once, if errors occur leave a blank cmap
     if (mCharacterMap) {
         return NS_OK;
     }
 
-    nsRefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+    nsRefPtr<gfxCharacterMap> charmap;
+    nsresult rv;
+    bool symbolFont;
+
+    if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
+                                                        mUVSOffset,
+                                                        symbolFont))) {
+        rv = NS_OK;
+    } else {
+        uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
+        charmap = new gfxCharacterMap();
+        AutoTable cmapTable(this, kCMAP);
 
-    uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
-    AutoTable cmapTable(this, kCMAP);
-    if (cmapTable) {
-        bool unicodeFont = false, symbolFont = false; // currently ignored
-        uint32_t cmapLen;
-        const uint8_t* cmapData =
-            reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable,
-                                                              &cmapLen));
-        rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen,
-                                    *charmap, mUVSOffset,
-                                    unicodeFont, symbolFont);
-    } else {
-        rv = NS_ERROR_NOT_AVAILABLE;
+        if (cmapTable) {
+            bool unicodeFont = false, symbolFont = false; // currently ignored
+            uint32_t cmapLen;
+            const uint8_t* cmapData =
+                reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable,
+                                                                  &cmapLen));
+            rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen,
+                                        *charmap, mUVSOffset,
+                                        unicodeFont, symbolFont);
+        } else {
+            rv = NS_ERROR_NOT_AVAILABLE;
+        }
     }
 
     mHasCmapTable = NS_SUCCEEDED(rv);
     if (mHasCmapTable) {
         gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
         mCharacterMap = pfl->FindCharMap(charmap);
     } else {
         // if error occurred, initialize to null cmap
@@ -1529,8 +1548,194 @@ gfxDWriteFontList::GlobalFontFallback(co
             *aMatchedFamily = family;
             return fontEntry;
         }
         Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, true);
     }
 
     return nullptr;
 }
+
+// used to load system-wide font info on off-main thread
+class DirectWriteFontInfo : public FontInfoData {
+public:
+    DirectWriteFontInfo(bool aLoadOtherNames,
+                        bool aLoadFaceNames,
+                        bool aLoadCmaps) :
+        FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps)
+    {}
+
+    virtual ~DirectWriteFontInfo() {}
+
+    // loads font data for all members of a given family
+    virtual void LoadFontFamilyData(const nsAString& aFamilyName);
+
+    nsRefPtr<IDWriteFontCollection> mSystemFonts;
+};
+
+void
+DirectWriteFontInfo::LoadFontFamilyData(const nsAString& aFamilyName)
+{
+    // lookup the family
+    nsAutoTArray<char16_t, 32> famName;
+
+    uint32_t len = aFamilyName.Length();
+    famName.SetLength(len + 1);
+    memcpy(famName.Elements(), aFamilyName.BeginReading(), len * sizeof(char16_t));
+    famName[len] = 0;
+
+    HRESULT hr;
+    BOOL exists = false;
+
+    uint32_t index;
+    hr = mSystemFonts->FindFamilyName(famName.Elements(), &index, &exists);
+    if (FAILED(hr) || !exists) {
+        return;
+    }
+
+    nsRefPtr<IDWriteFontFamily> family;
+    mSystemFonts->GetFontFamily(index, getter_AddRefs(family));
+    if (!family) {
+        return;
+    }
+
+    // later versions of DirectWrite support querying the fullname/psname
+    bool loadFaceNamesUsingDirectWrite = mLoadFaceNames;
+
+    for (uint32_t i = 0; i < family->GetFontCount(); i++) {
+        // get the font
+        nsRefPtr<IDWriteFont> dwFont;
+        hr = family->GetFont(i, getter_AddRefs(dwFont));
+        if (FAILED(hr)) {
+            // This should never happen.
+            NS_WARNING("Failed to get existing font from family.");
+            continue;
+        }
+
+        if (dwFont->GetSimulations() & DWRITE_FONT_SIMULATIONS_OBLIQUE) {
+            // We don't want these.
+            continue;
+        }
+
+        mLoadStats.fonts++;
+
+        // get the name of the face
+        nsString fullID(aFamilyName);
+        nsAutoString fontName;
+        hr = GetDirectWriteFontName(dwFont, fontName);
+        if (FAILED(hr)) {
+            continue;
+        }
+        fullID.Append(NS_LITERAL_STRING(" "));
+        fullID.Append(fontName);
+
+        FontFaceData fontData;
+        bool haveData = true;
+        nsRefPtr<IDWriteFontFace> dwFontFace;
+
+        if (mLoadFaceNames) {
+            // try to load using DirectWrite first
+            if (loadFaceNamesUsingDirectWrite) {
+                hr = GetDirectWriteFaceName(dwFont, PSNAME_ID, fontData.mPostscriptName);
+                if (FAILED(hr)) {
+                    loadFaceNamesUsingDirectWrite = false;
+                }
+                hr = GetDirectWriteFaceName(dwFont, FULLNAME_ID, fontData.mFullName);
+                if (FAILED(hr)) {
+                    loadFaceNamesUsingDirectWrite = false;
+                }
+            }
+
+            // if DirectWrite read fails, load directly from name table
+            if (!loadFaceNamesUsingDirectWrite) {
+                hr = dwFont->CreateFontFace(getter_AddRefs(dwFontFace));
+                if (SUCCEEDED(hr)) {
+                    uint32_t kNAME =
+                        NativeEndian::swapToBigEndian(TRUETYPE_TAG('n','a','m','e'));
+                    const char *nameData;
+                    BOOL exists;
+                    void* ctx;
+                    uint32_t nameSize;
+
+                    hr = dwFontFace->TryGetFontTable(
+                             kNAME,
+                             (const void**)&nameData, &nameSize, &ctx, &exists);
+
+                    if (SUCCEEDED(hr) && nameData && nameSize > 0) {
+                        gfxFontUtils::ReadCanonicalName(nameData, nameSize,
+                            gfxFontUtils::NAME_ID_FULL,
+                            fontData.mFullName);
+                        gfxFontUtils::ReadCanonicalName(nameData, nameSize,
+                            gfxFontUtils::NAME_ID_POSTSCRIPT,
+                            fontData.mPostscriptName);
+                        dwFontFace->ReleaseFontTable(ctx);
+                    }
+                }
+            }
+
+            haveData = !fontData.mPostscriptName.IsEmpty() ||
+                       !fontData.mFullName.IsEmpty();
+            if (haveData) {
+                mLoadStats.facenames++;
+            }
+        }
+
+        // cmaps
+        if (mLoadCmaps) {
+            if (!dwFontFace) {
+                hr = dwFont->CreateFontFace(getter_AddRefs(dwFontFace));
+                if (!SUCCEEDED(hr)) {
+                    continue;
+                }
+            }
+
+            uint32_t kCMAP =
+                NativeEndian::swapToBigEndian(TRUETYPE_TAG('c','m','a','p'));
+            const uint8_t *cmapData;
+            BOOL exists;
+            void* ctx;
+            uint32_t cmapSize;
+
+            hr = dwFontFace->TryGetFontTable(kCMAP,
+                     (const void**)&cmapData, &cmapSize, &ctx, &exists);
+
+            if (SUCCEEDED(hr)) {
+                bool cmapLoaded = false;
+                bool unicodeFont = false, symbolFont = false;
+                nsRefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+                uint32_t offset;
+
+                if (cmapData &&
+                    cmapSize > 0 &&
+                    NS_SUCCEEDED(
+                        gfxFontUtils::ReadCMAP(cmapData, cmapSize, *charmap,
+                                               offset, unicodeFont, symbolFont))) {
+                    fontData.mCharacterMap = charmap;
+                    fontData.mUVSOffset = offset;
+                    fontData.mSymbolFont = symbolFont;
+                    cmapLoaded = true;
+                    mLoadStats.cmaps++;
+                }
+                dwFontFace->ReleaseFontTable(ctx);
+                haveData = haveData || cmapLoaded;
+           }
+        }
+
+        // if have data, load
+        if (haveData) {
+            mFontFaceData.Put(fullID, fontData);
+        }
+    }
+}
+
+already_AddRefed<FontInfoData>
+gfxDWriteFontList::CreateFontInfoData()
+{
+    bool loadCmaps = !UsesSystemFallback() ||
+        gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+
+    nsRefPtr<DirectWriteFontInfo> fi =
+        new DirectWriteFontInfo(false, NeedFullnamePostscriptNames(), loadCmaps);
+    gfxWindowsPlatform::GetPlatform()->GetDWriteFactory()->
+        GetSystemFontCollection(getter_AddRefs(fi->mSystemFonts));
+
+    return fi.forget();
+}
--- a/gfx/thebes/gfxDWriteFontList.h
+++ b/gfx/thebes/gfxDWriteFontList.h
@@ -38,17 +38,17 @@ public:
      * \param aFamily IDWriteFontFamily object representing the directwrite
      * family object.
      */
     gfxDWriteFontFamily(const nsAString& aName, 
                         IDWriteFontFamily *aFamily)
       : gfxFontFamily(aName), mDWFamily(aFamily), mForceGDIClassic(false) {}
     virtual ~gfxDWriteFontFamily();
     
-    virtual void FindStyleVariations();
+    virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr);
 
     virtual void LocalizedName(nsAString& aLocalizedName);
 
     virtual void ReadFaceNames(gfxPlatformFontList *aPlatformFontList,
                                bool aNeedFullnamePostscriptNames);
 
     void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; }
 
@@ -144,17 +144,17 @@ public:
     }
 
     virtual ~gfxDWriteFontEntry();
 
     virtual bool IsSymbolFont();
 
     virtual hb_blob_t* GetFontTable(uint32_t aTableTag) MOZ_OVERRIDE;
 
-    nsresult ReadCMAP();
+    nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr);
 
     bool IsCJKFont();
 
     void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; }
     bool GetForceGDIClassic() { return mForceGDIClassic; }
 
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontListSizes* aSizes) const;
@@ -402,16 +402,18 @@ private:
      * Table of font substitutes, we grab this from the registry to get
      * alternative font names.
      */
     FontTable mFontSubstitutes;
 
     bool mInitialized;
     virtual nsresult DelayedInitFontList();
 
+    virtual already_AddRefed<FontInfoData> CreateFontInfoData();
+
     gfxFloat mForceGDIClassicMaxFontSize;
 
     // whether to use GDI font table access routines
     bool mGDIFontTableAccess;
     nsRefPtr<IDWriteGdiInterop> mGDIInterop;
 
     nsRefPtr<FontFallbackRenderer> mFallbackRenderer;
     nsRefPtr<IDWriteTextFormat>    mFallbackFormat;
--- a/gfx/thebes/gfxFT2FontList.cpp
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -458,17 +458,17 @@ FT2FontEntry::CairoFontFace()
         FTUserFontData *userFontData = new FTUserFontData(face, face.FontData());
         cairo_font_face_set_user_data(mFontFace, &sFTUserFontDataKey,
                                       userFontData, FTFontDestroyFunc);
     }
     return mFontFace;
 }
 
 nsresult
-FT2FontEntry::ReadCMAP()
+FT2FontEntry::ReadCMAP(FontInfoData *aFontInfoData)
 {
     if (mCharacterMap) {
         return NS_OK;
     }
 
     nsRefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
 
     AutoFallibleTArray<uint8_t,16384> buffer;
--- a/gfx/thebes/gfxFT2FontList.h
+++ b/gfx/thebes/gfxFT2FontList.h
@@ -69,17 +69,17 @@ public:
     // Create (if necessary) and return the cairo_font_face for this font.
     // This may fail and return null, so caller must be prepared to handle this.
     cairo_font_face_t *CairoFontFace();
 
     // Create a cairo_scaled_font for this face, with the given style.
     // This may fail and return null, so caller must be prepared to handle this.
     cairo_scaled_font_t *CreateScaledFont(const gfxFontStyle *aStyle);
 
-    nsresult ReadCMAP();
+    nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr);
 
     virtual hb_blob_t* GetFontTable(uint32_t aTableTag) MOZ_OVERRIDE;
 
     virtual nsresult CopyFontTable(uint32_t aTableTag,
                                    FallibleTArray<uint8_t>& aBuffer) MOZ_OVERRIDE;
 
     // Check for various kinds of brokenness, and set flags on the entry
     // accordingly so that we avoid using bad font tables
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -220,17 +220,17 @@ uint16_t gfxFontEntry::GetUVSGlyph(uint3
 
     if (mUVSData) {
         return gfxFontUtils::MapUVSToGlyphFormat14(mUVSData, aCh, aVS);
     }
 
     return 0;
 }
 
-nsresult gfxFontEntry::ReadCMAP()
+nsresult gfxFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
 {
     NS_ASSERTION(false, "using default no-op implementation of ReadCMAP");
     mCharacterMap = new gfxCharacterMap();
     return NS_OK;
 }
 
 nsString
 gfxFontEntry::RealFaceName()
@@ -559,16 +559,28 @@ gfxFontEntry::GetTableFromFontData(const
         return hb_blob_create(reinterpret_cast<const char*>(aFontData) +
                                   dir->offset, dir->length,
                               HB_MEMORY_MODE_READONLY, nullptr, nullptr);
 
     }
     return nullptr;
 }
 
+already_AddRefed<gfxCharacterMap>
+gfxFontEntry::GetCMAPFromFontInfo(FontInfoData *aFontInfoData,
+                                  uint32_t& aUVSOffset,
+                                  bool& aSymbolFont)
+{
+    if (!aFontInfoData || !aFontInfoData->mLoadCmaps) {
+        return nullptr;
+    }
+
+    return aFontInfoData->GetCMAP(mName, aUVSOffset, aSymbolFont);
+}
+
 hb_blob_t *
 gfxFontEntry::GetFontTable(uint32_t aTag)
 {
     hb_blob_t *blob;
     if (GetExistingFontTable(aTag, &blob)) {
         return blob;
     }
 
@@ -900,16 +912,21 @@ gfxFontFamily::FindFontForStyle(const gf
     }
 
     return matchFE;
 }
 
 void
 gfxFontFamily::CheckForSimpleFamily()
 {
+    // already checked this family
+    if (mIsSimpleFamily) {
+        return;
+    };
+
     uint32_t count = mAvailableFonts.Length();
     if (count > 4 || count == 0) {
         return; // can't be "simple" if there are >4 faces;
                 // if none then the family is unusable anyway
     }
 
     if (count == 1) {
         mIsSimpleFamily = true;
@@ -1142,69 +1159,84 @@ gfxFontFamily::SearchAllFontsForChar(Glo
                 aMatchData->mBestMatch = fe;
                 aMatchData->mMatchedFamily = this;
                 aMatchData->mMatchRank = rank;
             }
         }
     }
 }
 
+/*static*/ void
+gfxFontFamily::ReadOtherFamilyNamesForFace(const nsAString& aFamilyName,
+                                           const char *aNameData,
+                                           uint32_t aDataLength,
+                                           nsTArray<nsString>& aOtherFamilyNames,
+                                           bool useFullName)
+{
+    const gfxFontUtils::NameHeader *nameHeader =
+        reinterpret_cast<const gfxFontUtils::NameHeader*>(aNameData);
+
+    uint32_t nameCount = nameHeader->count;
+    if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) {
+        NS_WARNING("invalid font (name records)");
+        return;
+    }
+    
+    const gfxFontUtils::NameRecord *nameRecord =
+        reinterpret_cast<const gfxFontUtils::NameRecord*>(aNameData + sizeof(gfxFontUtils::NameHeader));
+    uint32_t stringsBase = uint32_t(nameHeader->stringOffset);
+
+    for (uint32_t i = 0; i < nameCount; i++, nameRecord++) {
+        uint32_t nameLen = nameRecord->length;
+        uint32_t nameOff = nameRecord->offset;  // offset from base of string storage
+
+        if (stringsBase + nameOff + nameLen > aDataLength) {
+            NS_WARNING("invalid font (name table strings)");
+            return;
+        }
+
+        uint16_t nameID = nameRecord->nameID;
+        if ((useFullName && nameID == gfxFontUtils::NAME_ID_FULL) ||
+            (!useFullName && (nameID == gfxFontUtils::NAME_ID_FAMILY ||
+                              nameID == gfxFontUtils::NAME_ID_PREFERRED_FAMILY))) {
+            nsAutoString otherFamilyName;
+            bool ok = gfxFontUtils::DecodeFontName(aNameData + stringsBase + nameOff,
+                                                     nameLen,
+                                                     uint32_t(nameRecord->platformID),
+                                                     uint32_t(nameRecord->encodingID),
+                                                     uint32_t(nameRecord->languageID),
+                                                     otherFamilyName);
+            // add if not same as canonical family name
+            if (ok && otherFamilyName != aFamilyName) {
+                aOtherFamilyNames.AppendElement(otherFamilyName);
+            }
+        }
+    }
+}
+
 // returns true if other names were found, false otherwise
 bool
 gfxFontFamily::ReadOtherFamilyNamesForFace(gfxPlatformFontList *aPlatformFontList,
                                            hb_blob_t           *aNameTable,
                                            bool                 useFullName)
 {
     uint32_t dataLength;
     const char *nameData = hb_blob_get_data(aNameTable, &dataLength);
-    const gfxFontUtils::NameHeader *nameHeader =
-        reinterpret_cast<const gfxFontUtils::NameHeader*>(nameData);
-
-    uint32_t nameCount = nameHeader->count;
-    if (nameCount * sizeof(gfxFontUtils::NameRecord) > dataLength) {
-        NS_WARNING("invalid font (name records)");
-        return false;
-    }
-    
-    const gfxFontUtils::NameRecord *nameRecord =
-        reinterpret_cast<const gfxFontUtils::NameRecord*>(nameData + sizeof(gfxFontUtils::NameHeader));
-    uint32_t stringsBase = uint32_t(nameHeader->stringOffset);
-
-    bool foundNames = false;
-    for (uint32_t i = 0; i < nameCount; i++, nameRecord++) {
-        uint32_t nameLen = nameRecord->length;
-        uint32_t nameOff = nameRecord->offset;  // offset from base of string storage
-
-        if (stringsBase + nameOff + nameLen > dataLength) {
-            NS_WARNING("invalid font (name table strings)");
-            return false;
-        }
-
-        uint16_t nameID = nameRecord->nameID;
-        if ((useFullName && nameID == gfxFontUtils::NAME_ID_FULL) ||
-            (!useFullName && (nameID == gfxFontUtils::NAME_ID_FAMILY ||
-                              nameID == gfxFontUtils::NAME_ID_PREFERRED_FAMILY))) {
-            nsAutoString otherFamilyName;
-            bool ok = gfxFontUtils::DecodeFontName(nameData + stringsBase + nameOff,
-                                                     nameLen,
-                                                     uint32_t(nameRecord->platformID),
-                                                     uint32_t(nameRecord->encodingID),
-                                                     uint32_t(nameRecord->languageID),
-                                                     otherFamilyName);
-            // add if not same as canonical family name
-            if (ok && otherFamilyName != mName) {
-                aPlatformFontList->AddOtherFamilyName(this, otherFamilyName);
-                foundNames = true;
-            }
-        }
-    }
-
-    return foundNames;
-}
-
+    nsAutoTArray<nsString,4> otherFamilyNames;
+
+    ReadOtherFamilyNamesForFace(mName, nameData, dataLength,
+                                otherFamilyNames, useFullName);
+
+    uint32_t n = otherFamilyNames.Length();
+    for (uint32_t i = 0; i < n; i++) {
+        aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]);
+    }
+
+    return n != 0;
+}
 
 void
 gfxFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList)
 {
     if (mOtherFamilyNamesInitialized) 
         return;
     mOtherFamilyNamesInitialized = true;
 
@@ -1246,40 +1278,92 @@ gfxFontFamily::ReadOtherFamilyNames(gfxP
             continue;
         }
         ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable);
     }
 }
 
 void
 gfxFontFamily::ReadFaceNames(gfxPlatformFontList *aPlatformFontList, 
-                             bool aNeedFullnamePostscriptNames)
+                             bool aNeedFullnamePostscriptNames,
+                             FontInfoData *aFontInfoData)
 {
     // if all needed names have already been read, skip
     if (mOtherFamilyNamesInitialized &&
         (mFaceNamesInitialized || !aNeedFullnamePostscriptNames))
         return;
 
-    FindStyleVariations();
+    if (!mOtherFamilyNamesInitialized &&
+        aFontInfoData &&
+        aFontInfoData->mLoadOtherNames)
+    {
+        nsAutoTArray<nsString,4> otherFamilyNames;
+        bool foundOtherNames =
+            aFontInfoData->GetOtherFamilyNames(mName, otherFamilyNames);
+        if (foundOtherNames) {
+            uint32_t i, n = otherFamilyNames.Length();
+            for (i = 0; i < n; i++) {
+                aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]);
+            }
+        }
+        mOtherFamilyNamesInitialized = true;
+    }
+
+    // if all needed data has been initialized, return
+    if (mOtherFamilyNamesInitialized &&
+        (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
+        return;
+    }
+
+    FindStyleVariations(aFontInfoData);
+
+    // check again, as style enumeration code may have loaded names
+    if (mOtherFamilyNamesInitialized &&
+        (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
+        return;
+    }
 
     uint32_t i, numFonts = mAvailableFonts.Length();
     const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e');
-    nsAutoString fullname, psname;
 
     bool firstTime = true, readAllFaces = false;
     for (i = 0; i < numFonts; ++i) {
         gfxFontEntry *fe = mAvailableFonts[i];
         if (!fe) {
             continue;
         }
+
+        nsAutoString fullname, psname;
+        bool foundFaceNames = false;
+        if (!mFaceNamesInitialized &&
+            aNeedFullnamePostscriptNames &&
+            aFontInfoData &&
+            aFontInfoData->mLoadFaceNames) {
+            aFontInfoData->GetFaceNames(fe->Name(), fullname, psname);
+            if (!fullname.IsEmpty()) {
+                aPlatformFontList->AddFullname(fe, fullname);
+            }
+            if (!psname.IsEmpty()) {
+                aPlatformFontList->AddPostscriptName(fe, psname);
+            }
+            foundFaceNames = true;
+
+            // found everything needed? skip to next font
+            if (mOtherFamilyNamesInitialized) {
+                continue;
+            }
+        }
+
+        // load directly from the name table
         gfxFontEntry::AutoTable nameTable(fe, kNAME);
         if (!nameTable) {
             continue;
         }
-        if (aNeedFullnamePostscriptNames) {
+
+        if (aNeedFullnamePostscriptNames && !foundFaceNames) {
             if (gfxFontUtils::ReadCanonicalName(
                     nameTable, gfxFontUtils::NAME_ID_FULL, fullname) == NS_OK)
             {
                 aPlatformFontList->AddFullname(fe, fullname);
             }
 
             if (gfxFontUtils::ReadCanonicalName(
                     nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK)
@@ -1321,16 +1405,35 @@ gfxFontFamily::FindFont(const nsAString&
         gfxFontEntry *fe = mAvailableFonts[i].get();
         if (fe && fe->Name() == aPostscriptName)
             return fe;
     }
     return nullptr;
 }
 
 void
+gfxFontFamily::ReadAllCMAPs(FontInfoData *aFontInfoData)
+{
+    FindStyleVariations(aFontInfoData);
+
+    uint32_t i, numFonts = mAvailableFonts.Length();
+    for (i = 0; i < numFonts; i++) {
+        gfxFontEntry *fe = mAvailableFonts[i];
+        // don't try to load cmaps for downloadable fonts not yet loaded
+        if (!fe || fe->mIsProxy) {
+            continue;
+        }
+        fe->ReadCMAP(aFontInfoData);
+        mFamilyCharacterMap.Union(*(fe->mCharacterMap));
+    }
+    mFamilyCharacterMap.Compact();
+    mFamilyCharacterMapInitialized = true;
+}
+
+void
 gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                       FontListSizes* aSizes) const
 {
     aSizes->mFontListSize +=
         mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
     aSizes->mCharMapsSize +=
         mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf);
 
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -44,16 +44,17 @@ class gfxFont;
 class gfxFontFamily;
 class gfxFontGroup;
 class gfxUserFontSet;
 class gfxUserFontData;
 class gfxShapedText;
 class gfxShapedWord;
 class gfxSVGGlyphs;
 class gfxTextContextPaint;
+class FontInfoData;
 
 class nsILanguageAtomService;
 
 #define FONT_MAX_SIZE                  2000.0
 
 #define NO_FONT_LANGUAGE_OVERRIDE      0
 
 struct FontListSizes;
@@ -294,17 +295,17 @@ public:
     uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS);
 
     // All concrete gfxFontEntry subclasses (except gfxProxyFontEntry) need
     // to override this, otherwise the font will never be used as it will
     // be considered to support no characters.
     // ReadCMAP() must *always* set the mCharacterMap pointer to a valid
     // gfxCharacterMap, even if empty, as other code assumes this pointer
     // can be safely dereferenced.
-    virtual nsresult ReadCMAP();
+    virtual nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr);
 
     bool TryGetSVGData(gfxFont* aFont);
     bool HasSVGGlyph(uint32_t aGlyphId);
     bool GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId,
                             gfxRect *aResult);
     bool RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, int aDrawMode,
                         gfxTextContextPaint *aContextPaint);
     // Call this when glyph geometry or rendering has changed
@@ -486,16 +487,22 @@ protected:
     // Return a blob that wraps a table found within a buffer of font data.
     // The blob does NOT own its data; caller guarantees that the buffer
     // will remain valid at least as long as the blob.
     // Returns null if the specified table is not found.
     // This method assumes aFontData is valid 'sfnt' data; before using this,
     // caller is responsible to do any sanitization/validation necessary.
     hb_blob_t* GetTableFromFontData(const void* aFontData, uint32_t aTableTag);
 
+    // lookup the cmap in cached font data
+    virtual already_AddRefed<gfxCharacterMap>
+    GetCMAPFromFontInfo(FontInfoData *aFontInfoData,
+                        uint32_t& aUVSOffset,
+                        bool& aSymbolFont);
+
     // Font's unitsPerEm from the 'head' table, if available (will be set to
     // kInvalidUPEM for non-sfnt font formats)
     uint16_t mUnitsPerEm;
 
     // Shaper-specific face objects, shared by all instantiations of the same
     // physical font, regardless of size.
     // Usually, only one of these will actually be created for any given font
     // entry, depending on the font tables that are present.
@@ -688,16 +695,17 @@ public:
         {
             aFontEntry->mIgnoreGDEF = true;
         }
         aFontEntry->mFamilyName = Name();
         mAvailableFonts.AppendElement(aFontEntry);
     }
 
     // note that the styles for this family have been added
+    bool HasStyles() { return mHasStyles; }
     void SetHasStyles(bool aHasStyles) { mHasStyles = aHasStyles; }
 
     // choose a specific face to match a style using CSS font matching
     // rules (weight matching occurs here).  may return a face that doesn't
     // precisely match (e.g. normal face when no italic face exists).
     // aNeedsSyntheticBold is set to true when synthetic bolding is
     // needed, false otherwise
     gfxFontEntry *FindFontForStyle(const gfxFontStyle& aFontStyle, 
@@ -708,48 +716,44 @@ public:
     void FindFontForChar(GlobalFontMatch *aMatchData);
 
     // checks all fonts for a matching font within the family
     void SearchAllFontsForChar(GlobalFontMatch *aMatchData);
 
     // read in other family names, if any, and use functor to add each into cache
     virtual void ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList);
 
+    // helper method for reading localized family names from the name table
+    // of a single face
+    static void ReadOtherFamilyNamesForFace(const nsAString& aFamilyName,
+                                            const char *aNameData,
+                                            uint32_t aDataLength,
+                                            nsTArray<nsString>& aOtherFamilyNames,
+                                            bool useFullName);
+
     // set when other family names have been read in
     void SetOtherFamilyNamesInitialized() {
         mOtherFamilyNamesInitialized = true;
     }
 
     // read in other localized family names, fullnames and Postscript names
     // for all faces and append to lookup tables
     virtual void ReadFaceNames(gfxPlatformFontList *aPlatformFontList,
-                               bool aNeedFullnamePostscriptNames);
+                               bool aNeedFullnamePostscriptNames,
+                               FontInfoData *aFontInfoData = nullptr);
 
     // find faces belonging to this family (platform implementations override this;
     // should be made pure virtual once all subclasses have been updated)
-    virtual void FindStyleVariations() { }
+    virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr) { }
 
     // search for a specific face using the Postscript name
     gfxFontEntry* FindFont(const nsAString& aPostscriptName);
 
     // read in cmaps for all the faces
-    void ReadAllCMAPs() {
-        uint32_t i, numFonts = mAvailableFonts.Length();
-        for (i = 0; i < numFonts; i++) {
-            gfxFontEntry *fe = mAvailableFonts[i];
-            // don't try to load cmaps for downloadable fonts not yet loaded
-            if (!fe || fe->mIsProxy) {
-                continue;
-            }
-            fe->ReadCMAP();
-            mFamilyCharacterMap.Union(*(fe->mCharacterMap));
-        }
-        mFamilyCharacterMap.Compact();
-        mFamilyCharacterMapInitialized = true;
-    }
+    void ReadAllCMAPs(FontInfoData *aFontInfoData = nullptr);
 
     bool TestCharacterMap(uint32_t aCh) {
         if (!mFamilyCharacterMapInitialized) {
             ReadAllCMAPs();
         }
         return mFamilyCharacterMap.test(aCh);
     }
 
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxFontInfoLoader.cpp
@@ -0,0 +1,238 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "gfxFontInfoLoader.h"
+#include "nsCRT.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"              // for nsRunnable
+#include "gfxPlatformFontList.h"
+
+using namespace mozilla;
+using mozilla::services::GetObserverService;
+
+void
+FontInfoData::Load()
+{
+    TimeStamp start = TimeStamp::Now();
+
+    uint32_t i, n = mFontFamiliesToLoad.Length();
+    mLoadStats.families = n;
+    for (i = 0; i < n; i++) {
+        LoadFontFamilyData(mFontFamiliesToLoad[i]);
+    }
+
+    mLoadTime = TimeStamp::Now() - start;
+}
+
+class FontInfoLoadCompleteEvent : public nsRunnable {
+    NS_DECL_THREADSAFE_ISUPPORTS
+
+    FontInfoLoadCompleteEvent(FontInfoData *aFontInfo) :
+        mFontInfo(aFontInfo)
+    {}
+    virtual ~FontInfoLoadCompleteEvent() {}
+
+    NS_IMETHOD Run();
+
+    nsRefPtr<FontInfoData> mFontInfo;
+};
+
+class AsyncFontInfoLoader : public nsRunnable {
+    NS_DECL_THREADSAFE_ISUPPORTS
+
+    AsyncFontInfoLoader(FontInfoData *aFontInfo) :
+        mFontInfo(aFontInfo)
+    {
+        mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo);
+    }
+    virtual ~AsyncFontInfoLoader() {}
+
+    NS_IMETHOD Run();
+
+    nsRefPtr<FontInfoData> mFontInfo;
+    nsRefPtr<FontInfoLoadCompleteEvent> mCompleteEvent;
+};
+
+// runs on main thread after async font info loading is done
+nsresult
+FontInfoLoadCompleteEvent::Run()
+{
+    gfxFontInfoLoader *loader =
+        static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList());
+
+    loader->FinalizeLoader(mFontInfo);
+
+    mFontInfo = nullptr;
+    return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS1(FontInfoLoadCompleteEvent, nsIRunnable);
+
+// runs on separate thread
+nsresult
+AsyncFontInfoLoader::Run()
+{
+    // load platform-specific font info
+    mFontInfo->Load();
+
+    // post a completion event that transfer the data to the fontlist
+    NS_DispatchToMainThread(mCompleteEvent, NS_DISPATCH_NORMAL);
+    mFontInfo = nullptr;
+
+    return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS1(AsyncFontInfoLoader, nsIRunnable);
+
+NS_IMPL_ISUPPORTS1(gfxFontInfoLoader::ShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports *aSubject,
+                                             const char *aTopic,
+                                             const char16_t *someData)
+{
+    if (!nsCRT::strcmp(aTopic, "quit-application")) {
+        mLoader->CancelLoader();
+    } else {
+        NS_NOTREACHED("unexpected notification topic");
+    }
+    return NS_OK;
+}
+
+void
+gfxFontInfoLoader::StartLoader(uint32_t aDelay, uint32_t aInterval)
+{
+    mInterval = aInterval;
+
+    // sanity check
+    if (mState != stateInitial &&
+        mState != stateTimerOff &&
+        mState != stateTimerOnDelay) {
+        CancelLoader();
+    }
+
+    // set up timer
+    if (!mTimer) {
+        mTimer = do_CreateInstance("@mozilla.org/timer;1");
+        if (!mTimer) {
+            NS_WARNING("Failure to create font info loader timer");
+            return;
+        }
+    }
+
+    AddShutdownObserver();
+
+    // delay? ==> start async thread after a delay
+    if (aDelay) {
+        mState = stateTimerOnDelay;
+        mTimer->InitWithFuncCallback(DelayedStartCallback, this, aDelay,
+                                     nsITimer::TYPE_ONE_SHOT);
+        return;
+    }
+
+    mFontInfo = CreateFontInfoData();
+
+    // initialize
+    InitLoader();
+
+    // start async load
+    mState = stateAsyncLoad;
+    nsresult rv = NS_NewNamedThread("Font Loader",
+                                    getter_AddRefs(mFontLoaderThread),
+                                    nullptr);
+    if (NS_FAILED(rv)) {
+        return;
+    }
+
+    nsCOMPtr<nsIRunnable> loadEvent = new AsyncFontInfoLoader(mFontInfo);
+
+    mFontLoaderThread->Dispatch(loadEvent, NS_DISPATCH_NORMAL);
+}
+
+void
+gfxFontInfoLoader::FinalizeLoader(FontInfoData *aFontInfo)
+{
+    // avoid loading data if loader has already been canceled
+    if (mState != stateAsyncLoad) {
+        return;
+    }
+
+    mLoadTime = mFontInfo->mLoadTime;
+
+    // try to load all font data immediately
+    if (LoadFontInfo()) {
+        CancelLoader();
+        return;
+    }
+
+    // not all work completed ==> run load on interval
+    mState = stateTimerOnInterval;
+    mTimer->InitWithFuncCallback(LoadFontInfoCallback, this, mInterval,
+                                 nsITimer::TYPE_REPEATING_SLACK);
+}
+
+void
+gfxFontInfoLoader::CancelLoader()
+{
+    if (mState == stateInitial) {
+        return;
+    }
+    mState = stateTimerOff;
+    if (mTimer) {
+        mTimer->Cancel();
+        mTimer = nullptr;
+    }
+    if (mFontLoaderThread) {
+        mFontLoaderThread->Shutdown();
+        mFontLoaderThread = nullptr;
+    }
+    RemoveShutdownObserver();
+    CleanupLoader();
+}
+
+void
+gfxFontInfoLoader::LoadFontInfoTimerFire()
+{
+    if (mState == stateTimerOnDelay) {
+        mState = stateTimerOnInterval;
+        mTimer->SetDelay(mInterval);
+    }
+
+    bool done = LoadFontInfo();
+    if (done) {
+        CancelLoader();
+    }
+}
+
+gfxFontInfoLoader::~gfxFontInfoLoader()
+{
+    RemoveShutdownObserver();
+}
+
+void
+gfxFontInfoLoader::AddShutdownObserver()
+{
+    if (mObserver) {
+        return;
+    }
+
+    nsCOMPtr<nsIObserverService> obs = GetObserverService();
+    if (obs) {
+        mObserver = new ShutdownObserver(this);
+        obs->AddObserver(mObserver, "quit-application", false);
+    }
+}
+
+void
+gfxFontInfoLoader::RemoveShutdownObserver()
+{
+    if (mObserver) {
+        nsCOMPtr<nsIObserverService> obs = GetObserverService();
+        if (obs) {
+            obs->RemoveObserver(mObserver, "quit-application");
+            mObserver = nullptr;
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxFontInfoLoader.h
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FONT_INFO_LOADER_H
+#define GFX_FONT_INFO_LOADER_H
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsIThread.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "gfxFont.h"
+#include "nsIRunnable.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTraceRefcnt.h"
+
+// data retrieved for a given face
+
+struct FontFaceData {
+    FontFaceData() : mUVSOffset(0), mSymbolFont(false) {}
+
+    FontFaceData(const FontFaceData& aFontFaceData) {
+        mFullName = aFontFaceData.mFullName;
+        mPostscriptName = aFontFaceData.mPostscriptName;
+        mCharacterMap = aFontFaceData.mCharacterMap;
+        mUVSOffset = aFontFaceData.mUVSOffset;
+        mSymbolFont = aFontFaceData.mSymbolFont;
+    }
+
+    nsString mFullName;
+    nsString mPostscriptName;
+    nsRefPtr<gfxCharacterMap> mCharacterMap;
+    uint32_t mUVSOffset;
+    bool mSymbolFont;
+};
+
+// base class used to contain cached system-wide font info.
+// methods in this class are called on off-main threads so
+// all methods use only static methods or other thread-safe
+// font data access API's. specifically, no use is made of
+// gfxPlatformFontList, gfxFontFamily, gfxFamily or any
+// harfbuzz API methods within FontInfoData subclasses.
+
+class FontInfoData {
+public:
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FontInfoData)
+
+    FontInfoData(bool aLoadOtherNames,
+                 bool aLoadFaceNames,
+                 bool aLoadCmaps) :
+        mLoadOtherNames(aLoadOtherNames),
+        mLoadFaceNames(aLoadFaceNames),
+        mLoadCmaps(aLoadCmaps)
+   {
+        MOZ_COUNT_CTOR(FontInfoData);
+   }
+
+    virtual ~FontInfoData() {
+        MOZ_COUNT_DTOR(FontInfoData);
+    }
+
+    virtual void Load();
+
+    // loads font data for all fonts of a given family
+    // (called on async thread)
+    virtual void LoadFontFamilyData(const nsAString& aFamilyName) = 0;
+
+    // -- methods overriden by platform-specific versions --
+
+    // fetches cmap data for a particular font from cached font data
+    virtual already_AddRefed<gfxCharacterMap>
+    GetCMAP(const nsAString& aFontName,
+            uint32_t& aUVSOffset,
+            bool& aSymbolFont)
+    {
+        FontFaceData faceData;
+        if (!mFontFaceData.Get(aFontName, &faceData) ||
+            !faceData.mCharacterMap) {
+            return nullptr;
+        }
+
+        aUVSOffset = faceData.mUVSOffset;
+        aSymbolFont = faceData.mSymbolFont;
+        nsRefPtr<gfxCharacterMap> cmap = faceData.mCharacterMap;
+        return cmap.forget();
+    }
+
+    // fetches fullname/postscript names from cached font data
+    virtual void GetFaceNames(const nsAString& aFontName,
+                              nsAString& aFullName,
+                              nsAString& aPostscriptName)
+    {
+        FontFaceData faceData;
+        if (!mFontFaceData.Get(aFontName, &faceData)) {
+            return;
+        }
+
+        aFullName = faceData.mFullName;
+        aPostscriptName = faceData.mPostscriptName;
+    }
+
+    // fetches localized family name data from cached font data
+    virtual bool GetOtherFamilyNames(const nsAString& aFamilyName,
+                                     nsTArray<nsString>& aOtherFamilyNames)
+    {
+        return mOtherFamilyNames.Get(aFamilyName, &aOtherFamilyNames); 
+    }
+
+    nsTArray<nsString> mFontFamiliesToLoad;
+
+    // time spent on the loader thread
+    mozilla::TimeDuration mLoadTime;
+
+    struct FontCounts {
+        uint32_t families;
+        uint32_t fonts;
+        uint32_t cmaps;
+        uint32_t facenames;
+        uint32_t othernames;
+    };
+
+    FontCounts mLoadStats;
+
+    bool mLoadOtherNames;
+    bool mLoadFaceNames;
+    bool mLoadCmaps;
+
+    // face name ==> per-face data
+    nsDataHashtable<nsStringHashKey, FontFaceData> mFontFaceData;
+
+    // canonical family name ==> array of localized family names
+    nsDataHashtable<nsStringHashKey, nsTArray<nsString> > mOtherFamilyNames;
+};
+
+// gfxFontInfoLoader - helper class for loading font info on async thread
+// For large, "all fonts on system" data, data needed on a given platform
+// (e.g. localized names, face names, cmaps) are loaded async.
+
+// helper class for loading in font info on a separate async thread
+// once async thread completes, completion process is run on regular
+// intervals to prevent tying up the main thread
+
+class gfxFontInfoLoader {
+public:
+
+    // state transitions:
+    //   initial ---StartLoader with delay---> timer on delay
+    //   initial ---StartLoader without delay---> timer on interval
+    //   timer on delay ---LoaderTimerFire---> timer on interval
+    //   timer on delay ---CancelLoader---> timer off
+    //   timer on interval ---CancelLoader---> timer off
+    //   timer off ---StartLoader with delay---> timer on delay
+    //   timer off ---StartLoader without delay---> timer on interval
+    typedef enum {
+        stateInitial,
+        stateTimerOnDelay,
+        stateAsyncLoad,
+        stateTimerOnInterval,
+        stateTimerOff
+    } TimerState;
+
+    gfxFontInfoLoader() :
+        mInterval(0), mState(stateInitial)
+    {
+    }
+
+    virtual ~gfxFontInfoLoader();
+
+    // start timer with an initial delay, then call Run method at regular intervals
+    void StartLoader(uint32_t aDelay, uint32_t aInterval);
+
+    // Finalize - async load complete, transfer data (on intervals if necessary)
+    virtual void FinalizeLoader(FontInfoData *aFontInfo);
+
+    // cancel the timer and cleanup
+    void CancelLoader();
+
+    uint32_t GetInterval() { return mInterval; }
+
+protected:
+    class ShutdownObserver : public nsIObserver
+    {
+    public:
+        NS_DECL_ISUPPORTS
+        NS_DECL_NSIOBSERVER
+
+        ShutdownObserver(gfxFontInfoLoader *aLoader)
+            : mLoader(aLoader)
+        { }
+
+        virtual ~ShutdownObserver()
+        { }
+
+    protected:
+        gfxFontInfoLoader *mLoader;
+    };
+
+    // CreateFontInfo - create platform-specific object used
+    //                  to load system-wide font info
+    virtual already_AddRefed<FontInfoData> CreateFontInfoData() {
+        return nullptr;
+    }
+
+    // Init - initialization before async loader thread runs
+    virtual void InitLoader() = 0;
+
+    // LoadFontInfo - transfer font info data within a time limit, return
+    //                true when done
+    virtual bool LoadFontInfo() = 0;
+
+    // Cleanup - finish and cleanup after done, including possible reflows
+    virtual void CleanupLoader() {
+        mFontInfo = nullptr;
+    }
+
+    // Timer interval callbacks
+    static void LoadFontInfoCallback(nsITimer *aTimer, void *aThis) {
+        gfxFontInfoLoader *loader = static_cast<gfxFontInfoLoader*>(aThis);
+        loader->LoadFontInfoTimerFire();
+    }
+
+    static void DelayedStartCallback(nsITimer *aTimer, void *aThis) {
+        gfxFontInfoLoader *loader = static_cast<gfxFontInfoLoader*>(aThis);
+        loader->StartLoader(0, loader->GetInterval());
+    }
+
+    void LoadFontInfoTimerFire();
+
+    void AddShutdownObserver();
+    void RemoveShutdownObserver();
+
+    nsCOMPtr<nsITimer> mTimer;
+    nsCOMPtr<nsIObserver> mObserver;
+    nsCOMPtr<nsIThread> mFontLoaderThread;
+    uint32_t mInterval;
+    TimerState mState;
+
+    // after async font loader completes, data is stored here
+    nsRefPtr<FontInfoData> mFontInfo;
+
+    // time spent on the loader thread
+    mozilla::TimeDuration mLoadTime;
+};
+
+#endif /* GFX_FONT_INFO_LOADER_H */
--- a/gfx/thebes/gfxFontUtils.cpp
+++ b/gfx/thebes/gfxFontUtils.cpp
@@ -13,37 +13,35 @@
 #include "gfxFontUtils.h"
 
 #include "nsServiceManagerUtils.h"
 
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 
+#include "nsCOMPtr.h"
 #include "nsIUUIDGenerator.h"
-#include "nsIObserverService.h"
 #include "nsIUnicodeDecoder.h"
-#include "nsCRT.h"
 
 #include "harfbuzz/hb.h"
 
 #include "plbase64.h"
 #include "prlog.h"
 
 #ifdef PR_LOGGING
 
 #define LOG(log, args) PR_LOG(gfxPlatform::GetLog(log), \
                                PR_LOG_DEBUG, args)
 
 #endif // PR_LOGGING
 
 #define UNICODE_BMP_LIMIT 0x10000
 
 using namespace mozilla;
-using mozilla::services::GetObserverService;
 
 #pragma pack(1)
 
 typedef struct {
     AutoSwap_PRUint16 format;
     AutoSwap_PRUint16 reserved;
     AutoSwap_PRUint32 length;
     AutoSwap_PRUint32 language;
@@ -1082,50 +1080,63 @@ enum {
     PLATFORM_ID       = gfxFontUtils::PLATFORM_ID_MAC
 #else
     CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MICROSOFT_EN_US,
     PLATFORM_ID       = gfxFontUtils::PLATFORM_ID_MICROSOFT
 #endif
 };    
 
 nsresult
-gfxFontUtils::ReadNames(hb_blob_t *aNameTable, uint32_t aNameID, 
-                        int32_t aPlatformID, nsTArray<nsString>& aNames)
+gfxFontUtils::ReadNames(const char *aNameData, uint32_t aDataLen,
+                        uint32_t aNameID, int32_t aPlatformID,
+                        nsTArray<nsString>& aNames)
 {
-    return ReadNames(aNameTable, aNameID, LANG_ALL, aPlatformID, aNames);
+    return ReadNames(aNameData, aDataLen, aNameID, LANG_ALL,
+                     aPlatformID, aNames);
 }
 
 nsresult
-gfxFontUtils::ReadCanonicalName(hb_blob_t *aNameTable, uint32_t aNameID, 
+gfxFontUtils::ReadCanonicalName(hb_blob_t *aNameTable, uint32_t aNameID,
                                 nsString& aName)
 {
+    uint32_t nameTableLen;
+    const char *nameTable = hb_blob_get_data(aNameTable, &nameTableLen);
+    return ReadCanonicalName(nameTable, nameTableLen, aNameID, aName);
+}
+
+nsresult
+gfxFontUtils::ReadCanonicalName(const char *aNameData, uint32_t aDataLen,
+                                uint32_t aNameID, nsString& aName)
+{
     nsresult rv;
     
     nsTArray<nsString> names;
     
     // first, look for the English name (this will succeed 99% of the time)
-    rv = ReadNames(aNameTable, aNameID, CANONICAL_LANG_ID, PLATFORM_ID, names);
+    rv = ReadNames(aNameData, aDataLen, aNameID, CANONICAL_LANG_ID, 
+                   PLATFORM_ID, names);
     NS_ENSURE_SUCCESS(rv, rv);
         
     // otherwise, grab names for all languages
     if (names.Length() == 0) {
-        rv = ReadNames(aNameTable, aNameID, LANG_ALL, PLATFORM_ID, names);
+        rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL,
+                       PLATFORM_ID, names);
         NS_ENSURE_SUCCESS(rv, rv);
     }
     
 #if defined(XP_MACOSX)
     // may be dealing with font that only has Microsoft name entries
     if (names.Length() == 0) {
-        rv = ReadNames(aNameTable, aNameID, LANG_ID_MICROSOFT_EN_US, 
+        rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ID_MICROSOFT_EN_US,
                        PLATFORM_ID_MICROSOFT, names);
         NS_ENSURE_SUCCESS(rv, rv);
         
         // getting really desperate now, take anything!
         if (names.Length() == 0) {
-            rv = ReadNames(aNameTable, aNameID, LANG_ALL, 
+            rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL,
                            PLATFORM_ID_MICROSOFT, names);
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 #endif
 
     // return the first name (99.9% of the time names will
     // contain a single English name)
@@ -1317,95 +1328,94 @@ gfxFontUtils::DecodeFontName(const char 
         return false;
     }
     aName.Truncate(destLength); // set the actual length
 
     return true;
 }
 
 nsresult
-gfxFontUtils::ReadNames(hb_blob_t *aNameTable, uint32_t aNameID, 
+gfxFontUtils::ReadNames(const char *aNameData, uint32_t aDataLen,
+                        uint32_t aNameID,
                         int32_t aLangID, int32_t aPlatformID,
                         nsTArray<nsString>& aNames)
 {
-    uint32_t nameTableLen;
-    const char *nameTable = hb_blob_get_data(aNameTable, &nameTableLen);
-    NS_ASSERTION(nameTableLen != 0, "null name table");
+    NS_ASSERTION(aDataLen != 0, "null name table");
 
-    if (!nameTableLen) {
+    if (!aDataLen) {
         return NS_ERROR_FAILURE;
     }
 
     // -- name table data
-    const NameHeader *nameHeader = reinterpret_cast<const NameHeader*>(nameTable);
+    const NameHeader *nameHeader = reinterpret_cast<const NameHeader*>(aNameData);
 
     uint32_t nameCount = nameHeader->count;
 
     // -- sanity check the number of name records
-    if (uint64_t(nameCount) * sizeof(NameRecord) > nameTableLen) {
+    if (uint64_t(nameCount) * sizeof(NameRecord) > aDataLen) {
         NS_WARNING("invalid font (name table data)");
         return NS_ERROR_FAILURE;
     }
-    
+
     // -- iterate through name records
-    const NameRecord *nameRecord 
-        = reinterpret_cast<const NameRecord*>(nameTable + sizeof(NameHeader));
+    const NameRecord *nameRecord
+        = reinterpret_cast<const NameRecord*>(aNameData + sizeof(NameHeader));
     uint64_t nameStringsBase = uint64_t(nameHeader->stringOffset);
 
     uint32_t i;
     for (i = 0; i < nameCount; i++, nameRecord++) {
         uint32_t platformID;
-        
+
         // skip over unwanted nameID's
         if (uint32_t(nameRecord->nameID) != aNameID)
             continue;
 
         // skip over unwanted platform data
         platformID = nameRecord->platformID;
-        if (aPlatformID != PLATFORM_ALL 
+        if (aPlatformID != PLATFORM_ALL
             && uint32_t(nameRecord->platformID) != PLATFORM_ID)
             continue;
-            
+
         // skip over unwanted languages
-        if (aLangID != LANG_ALL 
+        if (aLangID != LANG_ALL
               && uint32_t(nameRecord->languageID) != uint32_t(aLangID))
             continue;
-        
+
         // add name to names array
-        
+
         // -- calculate string location
         uint32_t namelen = nameRecord->length;
         uint32_t nameoff = nameRecord->offset;  // offset from base of string storage
 
-        if (nameStringsBase + uint64_t(nameoff) + uint64_t(namelen) 
-                > nameTableLen) {
+        if (nameStringsBase + uint64_t(nameoff) + uint64_t(namelen)
+                > aDataLen) {
             NS_WARNING("invalid font (name table strings)");
             return NS_ERROR_FAILURE;
         }
-        
+
         // -- decode if necessary and make nsString
         nsAutoString name;
-        
-        DecodeFontName(nameTable + nameStringsBase + nameoff, namelen,
+
+        DecodeFontName(aNameData + nameStringsBase + nameoff, namelen,
                        platformID, uint32_t(nameRecord->encodingID),
                        uint32_t(nameRecord->languageID), name);
-            
+
         uint32_t k, numNames;
         bool foundName = false;
-        
+
         numNames = aNames.Length();
         for (k = 0; k < numNames; k++) {
             if (name.Equals(aNames[k])) {
                 foundName = true;
                 break;
-            }    
+            }
         }
-        
+
         if (!foundName)
-            aNames.AppendElement(name);                          
+            aNames.AppendElement(name);
 
     }
 
     return NS_OK;
 }
 
 #ifdef XP_WIN
 
@@ -1416,111 +1426,8 @@ gfxFontUtils::IsCffFont(const uint8_t* a
     // this is only called after aFontData has passed basic validation,
     // so we know there is enough data present to allow us to read the version!
     const SFNTHeader *sfntHeader = reinterpret_cast<const SFNTHeader*>(aFontData);
     return (sfntHeader->sfntVersion == TRUETYPE_TAG('O','T','T','O'));
 }
 
 #endif
 
-NS_IMPL_ISUPPORTS1(gfxFontInfoLoader::ShutdownObserver, nsIObserver)
-
-NS_IMETHODIMP
-gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports *aSubject,
-                                             const char *aTopic,
-                                             const char16_t *someData)
-{
-    if (!nsCRT::strcmp(aTopic, "quit-application")) {
-        mLoader->CancelLoader();
-    } else {
-        NS_NOTREACHED("unexpected notification topic");
-    }
-    return NS_OK;
-}
-
-void
-gfxFontInfoLoader::StartLoader(uint32_t aDelay, uint32_t aInterval)
-{
-    mInterval = aInterval;
-
-    // sanity check
-    if (mState != stateInitial && mState != stateTimerOff) {
-        CancelLoader();
-    }
-
-    // set up timer
-    if (!mTimer) {
-        mTimer = do_CreateInstance("@mozilla.org/timer;1");
-        if (!mTimer) {
-            NS_WARNING("Failure to create font info loader timer");
-            return;
-        }
-    }
-
-    // need an initial delay?
-    uint32_t timerInterval;
-
-    if (aDelay) {
-        mState = stateTimerOnDelay;
-        timerInterval = aDelay;
-    } else {
-        mState = stateTimerOnInterval;
-        timerInterval = mInterval;
-    }
-
-    InitLoader();
-
-    // start timer
-    mTimer->InitWithFuncCallback(LoaderTimerCallback, this, timerInterval,
-                                 nsITimer::TYPE_REPEATING_SLACK);
-
-    nsCOMPtr<nsIObserverService> obs = GetObserverService();
-    if (obs) {
-        mObserver = new ShutdownObserver(this);
-        obs->AddObserver(mObserver, "quit-application", false);
-    }
-}
-
-void
-gfxFontInfoLoader::CancelLoader()
-{
-    if (mState == stateInitial) {
-        return;
-    }
-    mState = stateTimerOff;
-    if (mTimer) {
-        mTimer->Cancel();
-        mTimer = nullptr;
-    }
-    RemoveShutdownObserver();
-    FinishLoader();
-}
-
-void
-gfxFontInfoLoader::LoaderTimerFire()
-{
-    if (mState == stateTimerOnDelay) {
-        mState = stateTimerOnInterval;
-        mTimer->SetDelay(mInterval);
-    }
-
-    bool done = RunLoader();
-    if (done) {
-        CancelLoader();
-    }
-}
-
-gfxFontInfoLoader::~gfxFontInfoLoader()
-{
-    RemoveShutdownObserver();
-}
-
-void
-gfxFontInfoLoader::RemoveShutdownObserver()
-{
-    if (mObserver) {
-        nsCOMPtr<nsIObserverService> obs = GetObserverService();
-        if (obs) {
-            obs->RemoveObserver(mObserver, "quit-application");
-            mObserver = nullptr;
-        }
-    }
-}
--- a/gfx/thebes/gfxFontUtils.h
+++ b/gfx/thebes/gfxFontUtils.h
@@ -2,22 +2,19 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_FONT_UTILS_H
 #define GFX_FONT_UTILS_H
 
 #include "gfxPlatform.h"
-#include "nsITimer.h"
-#include "nsCOMPtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
-#include "nsIObserver.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Endian.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "zlib.h"
 #include <algorithm>
 
 /* Bug 341128 - w32api defines min/max which causes problems with <bitset> */
@@ -824,25 +821,29 @@ public:
     // create a new name table and build a new font with that name table
     // appended on the end, returns true on success
     static nsresult
     RenameFont(const nsAString& aName, const uint8_t *aFontData, 
                uint32_t aFontDataLength, FallibleTArray<uint8_t> *aNewFont);
     
     // read all names matching aNameID, returning in aNames array
     static nsresult
-    ReadNames(hb_blob_t *aNameTable, uint32_t aNameID, 
+    ReadNames(const char *aNameData, uint32_t aDataLen, uint32_t aNameID,
               int32_t aPlatformID, nsTArray<nsString>& aNames);
-      
+
     // reads English or first name matching aNameID, returning in aName
     // platform based on OS
     static nsresult
-    ReadCanonicalName(hb_blob_t *aNameTable, uint32_t aNameID, 
+    ReadCanonicalName(hb_blob_t *aNameTable, uint32_t aNameID,
                       nsString& aName);
-      
+
+    static nsresult
+    ReadCanonicalName(const char *aNameData, uint32_t aDataLen,
+                      uint32_t aNameID, nsString& aName);
+
     // convert a name from the raw name table data into an nsString,
     // provided we know how; return true if successful, or false
     // if we can't handle the encoding
     static bool
     DecodeFontName(const char *aBuf, int32_t aLength, 
                    uint32_t aPlatformCode, uint32_t aScriptCode,
                    uint32_t aLangCode, nsAString& dest);
 
@@ -911,17 +912,17 @@ public:
     static void GetPrefsFontList(const char *aPrefName, 
                                  nsTArray<nsString>& aFontList);
 
     // generate a unique font name
     static nsresult MakeUniqueUserFontName(nsAString& aName);
 
 protected:
     static nsresult
-    ReadNames(hb_blob_t *aNameTable, uint32_t aNameID, 
+    ReadNames(const char *aNameData, uint32_t aDataLen, uint32_t aNameID,
               int32_t aLangID, int32_t aPlatformID, nsTArray<nsString>& aNames);
 
     // convert opentype name-table platform/encoding/language values to a charset name
     // we can use to convert the name data to unicode, or "" if data is UTF16BE
     static const char*
     GetCharsetForFontName(uint16_t aPlatform, uint16_t aScript, uint16_t aLanguage);
 
     struct MacFontNameCharsetMapping {
@@ -934,85 +935,10 @@ protected:
                    ((mEncoding == rhs.mEncoding) && (mLanguage < rhs.mLanguage));
         }
     };
     static const MacFontNameCharsetMapping gMacFontNameCharsets[];
     static const char* gISOFontNameCharsets[];
     static const char* gMSFontNameCharsets[];
 };
 
-// helper class for loading in font info spaced out at regular intervals
-
-class gfxFontInfoLoader {
-public:
-
-    // state transitions:
-    //   initial ---StartLoader with delay---> timer on delay
-    //   initial ---StartLoader without delay---> timer on interval
-    //   timer on delay ---LoaderTimerFire---> timer on interval
-    //   timer on delay ---CancelLoader---> timer off
-    //   timer on interval ---CancelLoader---> timer off
-    //   timer off ---StartLoader with delay---> timer on delay
-    //   timer off ---StartLoader without delay---> timer on interval
-    typedef enum {
-        stateInitial,
-        stateTimerOnDelay,
-        stateTimerOnInterval,
-        stateTimerOff
-    } TimerState;
-
-    gfxFontInfoLoader() :
-        mInterval(0), mState(stateInitial)
-    {
-    }
-
-    virtual ~gfxFontInfoLoader();
-
-    // start timer with an initial delay, then call Run method at regular intervals
-    void StartLoader(uint32_t aDelay, uint32_t aInterval);
-
-    // cancel the timer and cleanup
-    void CancelLoader();
-
-protected:
-    class ShutdownObserver : public nsIObserver
-    {
-    public:
-        NS_DECL_ISUPPORTS
-        NS_DECL_NSIOBSERVER
-
-        ShutdownObserver(gfxFontInfoLoader *aLoader)
-            : mLoader(aLoader)
-        { }
-
-        virtual ~ShutdownObserver()
-        { }
-
-    protected:
-        gfxFontInfoLoader *mLoader;
-    };
-
-    // Init - initialization at start time after initial delay
-    virtual void InitLoader() = 0;
-
-    // Run - called at intervals, return true to indicate done
-    virtual bool RunLoader() = 0;
-
-    // Finish - cleanup after done
-    virtual void FinishLoader() = 0;
-
-    // Timer interval callbacks
-    static void LoaderTimerCallback(nsITimer *aTimer, void *aThis) {
-        gfxFontInfoLoader *loader = static_cast<gfxFontInfoLoader*>(aThis);
-        loader->LoaderTimerFire();
-    }
-
-    void LoaderTimerFire();
-
-    void RemoveShutdownObserver();
-
-    nsCOMPtr<nsITimer> mTimer;
-    nsCOMPtr<nsIObserver> mObserver;
-    uint32_t mInterval;
-    TimerState mState;
-};
 
 #endif /* GFX_FONT_UTILS_H */
--- a/gfx/thebes/gfxGDIFontList.cpp
+++ b/gfx/thebes/gfxGDIFontList.cpp
@@ -141,49 +141,55 @@ GDIFontEntry::GDIFontEntry(const nsAStri
     if (IsType1())
         mForceGDI = true;
     mIsUserFont = aUserFontData != nullptr;
 
     InitLogFont(aFaceName, aFontType);
 }
 
 nsresult
-GDIFontEntry::ReadCMAP()
+GDIFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
 {
     // attempt this once, if errors occur leave a blank cmap
     if (mCharacterMap) {
         return NS_OK;
     }
 
     // skip non-SFNT fonts completely
     if (mFontType != GFX_FONT_TYPE_PS_OPENTYPE && 
         mFontType != GFX_FONT_TYPE_TT_OPENTYPE &&
         mFontType != GFX_FONT_TYPE_TRUETYPE) 
     {
         mCharacterMap = new gfxCharacterMap();
         mCharacterMap->mBuildOnTheFly = true;
         return NS_ERROR_FAILURE;
     }
 
-    nsRefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
-
-    uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
+    nsRefPtr<gfxCharacterMap> charmap;
     nsresult rv;
-
-    AutoFallibleTArray<uint8_t,16384> cmap;
-    rv = CopyFontTable(kCMAP, cmap);
+    bool unicodeFont = false, symbolFont = false;
 
-    bool unicodeFont = false, symbolFont = false; // currently ignored
+    if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
+                                                        mUVSOffset,
+                                                        symbolFont))) {
+        mSymbolFont = symbolFont;
+        rv = NS_OK;
+    } else {
+        uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
+        charmap = new gfxCharacterMap();
+        AutoFallibleTArray<uint8_t,16384> cmap;
+        rv = CopyFontTable(kCMAP, cmap);
 
-    if (NS_SUCCEEDED(rv)) {
-        rv = gfxFontUtils::ReadCMAP(cmap.Elements(), cmap.Length(),
-                                    *charmap, mUVSOffset,
-                                    unicodeFont, symbolFont);
+        if (NS_SUCCEEDED(rv)) {
+            rv = gfxFontUtils::ReadCMAP(cmap.Elements(), cmap.Length(),
+                                        *charmap, mUVSOffset,
+                                        unicodeFont, symbolFont);
+        }
+        mSymbolFont = symbolFont;
     }
-    mSymbolFont = symbolFont;
 
     mHasCmapTable = NS_SUCCEEDED(rv);
     if (mHasCmapTable) {
         gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
         mCharacterMap = pfl->FindCharMap(charmap);
     } else {
         // if error occurred, initialize to null cmap
         mCharacterMap = new gfxCharacterMap();
@@ -507,17 +513,17 @@ GDIFontFamily::FamilyAddStylesProc(const
              (logFont.lfItalic == 0xff) ? "italic" : "normal",
              logFont.lfWeight, fe->Stretch()));
     }
 #endif
     return 1;
 }
 
 void
-GDIFontFamily::FindStyleVariations()
+GDIFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
 {
     if (mHasStyles)
         return;
     mHasStyles = true;
 
     HDC hdc = GetDC(nullptr);
     SetGraphicsMode(hdc, GM_ADVANCED);
 
@@ -904,8 +910,192 @@ gfxGDIFontList::AddSizeOfExcludingThis(M
 
 void
 gfxGDIFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                                        FontListSizes* aSizes) const
 {
     aSizes->mFontListSize += aMallocSizeOf(this);
     AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
+
+// used to load system-wide font info on off-main thread
+class GDIFontInfo : public FontInfoData {
+public:
+    GDIFontInfo(bool aLoadOtherNames,
+                bool aLoadFaceNames,
+                bool aLoadCmaps) :
+        FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps)
+    {}
+
+    virtual ~GDIFontInfo() {}
+
+    virtual void Load() {
+        mHdc = GetDC(nullptr);
+        SetGraphicsMode(mHdc, GM_ADVANCED);
+        FontInfoData::Load();
+        ReleaseDC(nullptr, mHdc);
+    }
+
+    // loads font data for all members of a given family
+    virtual void LoadFontFamilyData(const nsAString& aFamilyName);
+
+    // callback for GDI EnumFontFamiliesExW call
+    static int CALLBACK EnumerateFontsForFamily(const ENUMLOGFONTEXW *lpelfe,
+                                                const NEWTEXTMETRICEXW *nmetrics,
+                                                DWORD fontType, LPARAM data);
+
+    HDC mHdc;
+};
+
+struct EnumerateFontsForFamilyData {
+    EnumerateFontsForFamilyData(const nsAString& aFamilyName,
+                                GDIFontInfo& aFontInfo)
+        : mFamilyName(aFamilyName), mFontInfo(aFontInfo)
+    {}
+
+    nsString mFamilyName;
+    nsTArray<nsString> mOtherFamilyNames;
+    GDIFontInfo& mFontInfo;
+    nsString mPreviousFontName;
+};
+
+int CALLBACK GDIFontInfo::EnumerateFontsForFamily(
+                 const ENUMLOGFONTEXW *lpelfe,
+                 const NEWTEXTMETRICEXW *nmetrics,
+                 DWORD fontType, LPARAM data)
+{
+    EnumerateFontsForFamilyData *famData =
+        reinterpret_cast<EnumerateFontsForFamilyData*>(data);
+    HDC hdc = famData->mFontInfo.mHdc;
+    LOGFONTW logFont = lpelfe->elfLogFont;
+    const NEWTEXTMETRICW& metrics = nmetrics->ntmTm;
+
+    AutoSelectFont font(hdc, &logFont);
+    if (!font.IsValid()) {
+        return 1;
+    }
+
+    FontFaceData fontData;
+    nsDependentString fontName(lpelfe->elfFullName);
+
+    // callback called for each style-charset so return if style already seen
+    if (fontName.Equals(famData->mPreviousFontName)) {
+        return 1;
+    }
+    famData->mPreviousFontName = fontName;
+    famData->mFontInfo.mLoadStats.fonts++;
+
+    // read name table info
+    bool nameDataLoaded = false;
+    if (famData->mFontInfo.mLoadFaceNames || famData->mFontInfo.mLoadOtherNames) {
+        uint32_t kNAME =
+            NativeEndian::swapToBigEndian(TRUETYPE_TAG('n','a','m','e'));
+        uint32_t nameSize;
+        nsAutoTArray<uint8_t, 1024> nameData;
+
+        nameSize = ::GetFontData(hdc, kNAME, 0, nullptr, 0);
+        if (nameSize != GDI_ERROR &&
+            nameSize > 0 &&
+            nameData.SetLength(nameSize)) {
+            ::GetFontData(hdc, kNAME, 0, nameData.Elements(), nameSize);
+
+            // face names
+            if (famData->mFontInfo.mLoadFaceNames) {
+                gfxFontUtils::ReadCanonicalName((const char*)(nameData.Elements()), nameSize,
+                                                gfxFontUtils::NAME_ID_FULL,
+                                                fontData.mFullName);
+                gfxFontUtils::ReadCanonicalName((const char*)(nameData.Elements()), nameSize,
+                                                gfxFontUtils::NAME_ID_POSTSCRIPT,
+                                                fontData.mPostscriptName);
+                nameDataLoaded = true;
+                famData->mFontInfo.mLoadStats.facenames++;
+            }
+
+            // other family names
+            if (famData->mFontInfo.mLoadOtherNames) {
+                gfxFontFamily::ReadOtherFamilyNamesForFace(famData->mFamilyName,
+                                                           (const char*)(nameData.Elements()),
+                                                           nameSize,
+                                                           famData->mOtherFamilyNames,
+                                                           false);
+            }
+        }
+    }
+
+    // read cmap
+    bool cmapLoaded = false;
+    gfxWindowsFontType feType =
+        GDIFontEntry::DetermineFontType(metrics, fontType);
+    if (famData->mFontInfo.mLoadCmaps &&
+        (feType == GFX_FONT_TYPE_PS_OPENTYPE ||
+         feType == GFX_FONT_TYPE_TT_OPENTYPE ||
+         feType == GFX_FONT_TYPE_TRUETYPE))
+    {
+        uint32_t kCMAP =
+            NativeEndian::swapToBigEndian(TRUETYPE_TAG('c','m','a','p'));
+        uint32_t cmapSize;
+        nsAutoTArray<uint8_t, 1024> cmapData;
+
+        cmapSize = ::GetFontData(hdc, kCMAP, 0, nullptr, 0);
+        if (cmapSize != GDI_ERROR &&
+            cmapSize > 0 &&
+            cmapData.SetLength(cmapSize)) {
+            ::GetFontData(hdc, kCMAP, 0, cmapData.Elements(), cmapSize);
+            bool cmapLoaded = false;
+            bool unicodeFont = false, symbolFont = false;
+            nsRefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+            uint32_t offset;
+
+            if (NS_SUCCEEDED(gfxFontUtils::ReadCMAP(cmapData.Elements(),
+                                                    cmapSize, *charmap,
+                                                    offset, unicodeFont,
+                                                    symbolFont))) {
+                fontData.mCharacterMap = charmap;
+                fontData.mUVSOffset = offset;
+                fontData.mSymbolFont = symbolFont;
+                cmapLoaded = true;
+                famData->mFontInfo.mLoadStats.cmaps++;
+            }
+        }
+    }
+
+    if (cmapLoaded || nameDataLoaded) {
+        famData->mFontInfo.mFontFaceData.Put(fontName, fontData);
+    }
+
+    return 1;
+}
+
+void
+GDIFontInfo::LoadFontFamilyData(const nsAString& aFamilyName)
+{
+    // iterate over the family
+    LOGFONTW logFont;
+    memset(&logFont, 0, sizeof(LOGFONTW));
+    logFont.lfCharSet = DEFAULT_CHARSET;
+    logFont.lfPitchAndFamily = 0;
+    uint32_t l = std::min<uint32_t>(aFamilyName.Length(), LF_FACESIZE - 1);
+    memcpy(logFont.lfFaceName, aFamilyName.BeginReading(), l * sizeof(char16_t));
+
+    EnumerateFontsForFamilyData data(aFamilyName, *this);
+
+    EnumFontFamiliesExW(mHdc, &logFont,
+                        (FONTENUMPROCW)GDIFontInfo::EnumerateFontsForFamily,
+                        (LPARAM)(&data), 0);
+
+    // if found other names, insert them
+    if (data.mOtherFamilyNames.Length() != 0) {
+        mOtherFamilyNames.Put(aFamilyName, data.mOtherFamilyNames);
+        mLoadStats.othernames += data.mOtherFamilyNames.Length();
+    }
+}
+
+already_AddRefed<FontInfoData>
+gfxGDIFontList::CreateFontInfoData()
+{
+    bool loadCmaps = !UsesSystemFallback() ||
+        gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+
+    nsRefPtr<GDIFontInfo> fi =
+        new GDIFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps);
+
+    return fi.forget();
+}
--- a/gfx/thebes/gfxGDIFontList.h
+++ b/gfx/thebes/gfxGDIFontList.h
@@ -103,17 +103,17 @@ enum gfxWindowsFontType {
 // A single member of a font family (i.e. a single face, such as Times Italic)
 // represented as a LOGFONT that will resolve to the correct face.
 // This replaces FontEntry from gfxWindowsFonts.h/cpp.
 class GDIFontEntry : public gfxFontEntry
 {
 public:
     LPLOGFONTW GetLogFont() { return &mLogFont; }
 
-    nsresult ReadCMAP();
+    nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr);
 
     virtual bool IsSymbolFont();
 
     void FillLogFont(LOGFONTW *aLogFont, uint16_t aWeight, gfxFloat aSize,
                      bool aUseCleartype);
 
     static gfxWindowsFontType DetermineFontType(const NEWTEXTMETRICW& metrics, 
                                                 DWORD fontType)
@@ -289,17 +289,17 @@ protected:
 
 // a single font family, referencing one or more faces
 class GDIFontFamily : public gfxFontFamily
 {
 public:
     GDIFontFamily(nsAString &aName) :
         gfxFontFamily(aName) {}
 
-    virtual void FindStyleVariations();
+    virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr);
 
 private:
     static int CALLBACK FamilyAddStylesProc(const ENUMLOGFONTEXW *lpelfe,
                                             const NEWTEXTMETRICEXW *nmetrics,
                                             DWORD fontType, LPARAM data);
 };
 
 class gfxGDIFontList : public gfxPlatformFontList {
@@ -334,15 +334,17 @@ private:
 
     nsresult GetFontSubstitutes();
 
     static int CALLBACK EnumFontFamExProc(ENUMLOGFONTEXW *lpelfe,
                                           NEWTEXTMETRICEXW *lpntme,
                                           DWORD fontType,
                                           LPARAM lParam);
 
+    virtual already_AddRefed<FontInfoData> CreateFontInfoData();
+
     typedef nsRefPtrHashtable<nsStringHashKey, gfxFontFamily> FontTable;
 
     FontTable mFontSubstitutes;
     nsTArray<nsString> mNonExistingFonts;
 };
 
 #endif /* GFX_GDIFONTLIST_H */
--- a/gfx/thebes/gfxMacPlatformFontList.h
+++ b/gfx/thebes/gfxMacPlatformFontList.h
@@ -43,17 +43,17 @@ public:
 
     // override gfxFontEntry table access function to bypass table cache,
     // use CGFontRef API to get direct access to system font data
     virtual hb_blob_t *GetFontTable(uint32_t aTag) MOZ_OVERRIDE;
 
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontListSizes* aSizes) const;
 
-    nsresult ReadCMAP();
+    nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr);
 
     bool RequiresAATLayout() const { return mRequiresAAT; }
 
     bool IsCFF();
 
 protected:
     virtual gfxFont* CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold);
 
@@ -111,16 +111,18 @@ private:
     virtual gfxFontEntry* GlobalFontFallback(const uint32_t aCh,
                                              int32_t aRunScript,
                                              const gfxFontStyle* aMatchStyle,
                                              uint32_t& aCmapCount,
                                              gfxFontFamily** aMatchedFamily);
 
     virtual bool UsesSystemFallback() { return true; }
 
+    virtual already_AddRefed<FontInfoData> CreateFontInfoData();
+
     enum {
         kATSGenerationInitial = -1
     };
 
     // default font for use with system-wide font fallback
     CTFontRef mDefaultFont;
 };
 
--- a/gfx/thebes/gfxMacPlatformFontList.mm
+++ b/gfx/thebes/gfxMacPlatformFontList.mm
@@ -217,40 +217,48 @@ SupportsScriptInGSUB(gfxFontEntry* aFont
         hb_ot_layout_table_choose_script(face, TRUETYPE_TAG('G','S','U','B'),
                                          aScriptTags, &index, &chosenScript);
     hb_face_destroy(face);
 
     return found && chosenScript != TRUETYPE_TAG('D','F','L','T');
 }
 
 nsresult
-MacOSFontEntry::ReadCMAP()
+MacOSFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
 {
     // attempt this once, if errors occur leave a blank cmap
     if (mCharacterMap) {
         return NS_OK;
     }
 
-    nsRefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+    nsRefPtr<gfxCharacterMap> charmap;
+    nsresult rv;
+    bool symbolFont;
 
-    uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
-
-    AutoTable cmapTable(this, kCMAP);
-    nsresult rv;
+    if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
+                                                        mUVSOffset,
+                                                        symbolFont))) {
+        rv = NS_OK;
+    } else {
+        uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
+        charmap = new gfxCharacterMap();
+        AutoTable cmapTable(this, kCMAP);
 
-    if (cmapTable) {
-        bool unicodeFont = false, symbolFont = false; // currently ignored
-
-        uint32_t cmapLen;
-        const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen);
-        rv = gfxFontUtils::ReadCMAP((const uint8_t*)cmapData, cmapLen,
-                                    *charmap, mUVSOffset,
-                                    unicodeFont, symbolFont);
-    } else {
-        rv = NS_ERROR_NOT_AVAILABLE;
+        if (cmapTable) {
+            bool unicodeFont = false, symbolFont = false; // currently ignored
+            uint32_t cmapLen;
+            const uint8_t* cmapData =
+                reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable,
+                                                                  &cmapLen));
+            rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen,
+                                        *charmap, mUVSOffset,
+                                        unicodeFont, symbolFont);
+        } else {
+            rv = NS_ERROR_NOT_AVAILABLE;
+        }
     }
 
     if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) {
         // We assume a Graphite font knows what it's doing,
         // and provides whatever shaping is needed for the
         // characters it supports, so only check/clear the
         // complex-script ranges for non-Graphite fonts
 
@@ -441,17 +449,17 @@ public:
     gfxMacFontFamily(nsAString& aName) :
         gfxFontFamily(aName)
     {}
 
     virtual ~gfxMacFontFamily() {}
 
     virtual void LocalizedName(nsAString& aLocalizedName);
 
-    virtual void FindStyleVariations();
+    virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr);
 };
 
 void
 gfxMacFontFamily::LocalizedName(nsAString& aLocalizedName)
 {
     nsAutoreleasePool localPool;
 
     if (!HasOtherFamilyNames()) {
@@ -469,17 +477,17 @@ gfxMacFontFamily::LocalizedName(nsAStrin
         return;
     }
 
     // failed to get localized name, just use the canonical one
     aLocalizedName = mName;
 }
 
 void
-gfxMacFontFamily::FindStyleVariations()
+gfxMacFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
 {
     if (mHasStyles)
         return;
 
     nsAutoreleasePool localPool;
 
     NSString *family = GetNSStringForString(mName);
 
@@ -673,40 +681,58 @@ gfxMacPlatformFontList::InitFontList()
     nsAutoreleasePool localPool;
 
     Telemetry::AutoTimer<Telemetry::MAC_INITFONTLIST_TOTAL> timer;
 
     // reset font lists
     gfxPlatformFontList::InitFontList();
     
     // iterate over available families
-    NSEnumerator *families = [[sFontManager availableFontFamilies]
-                              objectEnumerator];  // returns "canonical", non-localized family name
+
+    CFArrayRef familyNames = CTFontManagerCopyAvailableFontFamilyNames();
 
-    nsAutoString availableFamilyName;
+    // iterate over families
+    uint32_t i, numFamilies;
+
+    numFamilies = CFArrayGetCount(familyNames);
+    for (i = 0; i < numFamilies; i++) {
+        CFStringRef family = (CFStringRef)CFArrayGetValueAtIndex(familyNames, i);
 
-    NSString *availableFamily = nil;
-    while ((availableFamily = [families nextObject])) {
+        // CTFontManager includes weird internal family names and
+        // LastResort, skip over those
+        if (!family ||
+            ::CFStringHasPrefix(family, CFSTR(".")) ||
+            CFStringCompare(family, CFSTR("LastResort"),
+                            kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
+            continue;
+        }
 
-        // make a nsString
-        GetStringForNSString(availableFamily, availableFamilyName);
+        nsAutoTArray<UniChar, 1024> buffer;
+        CFIndex len = ::CFStringGetLength(family);
+        buffer.SetLength(len+1);
+        ::CFStringGetCharacters(family, ::CFRangeMake(0, len),
+                                buffer.Elements());
+        buffer[len] = 0;
+        nsAutoString familyName(reinterpret_cast<char16_t*>(buffer.Elements()), len);
 
         // create a family entry
-        gfxFontFamily *familyEntry = new gfxMacFontFamily(availableFamilyName);
+        gfxFontFamily *familyEntry = new gfxMacFontFamily(familyName);
         if (!familyEntry) break;
 
         // add the family entry to the hash table
-        ToLowerCase(availableFamilyName);
-        mFontFamilies.Put(availableFamilyName, familyEntry);
+        ToLowerCase(familyName);
+        mFontFamilies.Put(familyName, familyEntry);
 
         // check the bad underline blacklist
-        if (mBadUnderlineFamilyNames.Contains(availableFamilyName))
+        if (mBadUnderlineFamilyNames.Contains(familyName))
             familyEntry->SetBadUnderlineFamily();
     }
 
+    CFRelease(familyNames);
+
     InitSingleFaceList();
 
     // to avoid full search of font name tables, seed the other names table with localized names from
     // some of the prefs fonts which are accessed via their localized names.  changes in the pref fonts will only cause
     // a font lookup miss earlier. this is a simple optimization, it's not required for correctness
     PreloadNamesList();
 
     // start the delayed cmap loader
@@ -991,8 +1017,144 @@ gfxMacPlatformFontList::MakePlatformFont
     // if something is funky about this font, delete immediately
 
 #if DEBUG
     NS_WARNING("downloaded font not loaded properly");
 #endif
 
     return nullptr;
 }
+
+// used to load system-wide font info on off-main thread
+class MacFontInfo : public FontInfoData {
+public:
+    MacFontInfo(bool aLoadOtherNames,
+                bool aLoadFaceNames,
+                bool aLoadCmaps) :
+        FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps)
+    {}
+
+    virtual ~MacFontInfo() {}
+
+    virtual void Load() {
+        nsAutoreleasePool localPool;
+        FontInfoData::Load();
+    }
+
+    // loads font data for all members of a given family
+    virtual void LoadFontFamilyData(const nsAString& aFamilyName);
+};
+
+void
+MacFontInfo::LoadFontFamilyData(const nsAString& aFamilyName)
+{
+    // family name ==> CTFontDescriptor
+    NSString *famName = GetNSStringForString(aFamilyName);
+    CFStringRef family = CFStringRef(famName);
+
+    CFMutableDictionaryRef attr =
+        CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
+                                  &kCFTypeDictionaryValueCallBacks);
+    CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family);
+    CTFontDescriptorRef fd = CTFontDescriptorCreateWithAttributes(attr);
+    CFRelease(attr);
+    CFArrayRef matchingFonts =
+        CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL);
+    CFRelease(fd);
+    if (!matchingFonts) {
+        return;
+    }
+
+    nsTArray<nsString> otherFamilyNames;
+    bool hasOtherFamilyNames = true;
+
+    // iterate over faces in the family
+    int f, numFaces = (int) CFArrayGetCount(matchingFonts);
+    for (f = 0; f < numFaces; f++) {
+        mLoadStats.fonts++;
+
+        CTFontDescriptorRef faceDesc =
+            (CTFontDescriptorRef)CFArrayGetValueAtIndex(matchingFonts, f);
+        if (!faceDesc) {
+            continue;
+        }
+        CTFontRef fontRef = CTFontCreateWithFontDescriptor(faceDesc,
+                                                           0.0, nullptr);
+
+        if (mLoadCmaps) {
+            // face name
+            CFStringRef faceName = (CFStringRef)
+                CTFontDescriptorCopyAttribute(faceDesc, kCTFontNameAttribute);
+
+            nsAutoTArray<UniChar, 1024> buffer;
+            CFIndex len = CFStringGetLength(faceName);
+            buffer.SetLength(len+1);
+            CFStringGetCharacters(faceName, ::CFRangeMake(0, len),
+                                    buffer.Elements());
+            buffer[len] = 0;
+            nsAutoString fontName(reinterpret_cast<char16_t*>(buffer.Elements()),
+                                  len);
+
+            // load the cmap data
+            FontFaceData fontData;
+            CFDataRef cmapTable = CTFontCopyTable(fontRef, kCTFontTableCmap,
+                                                 kCTFontTableOptionNoOptions);
+            if (cmapTable) {
+                bool unicodeFont = false, symbolFont = false; // ignored
+                const uint8_t *cmapData =
+                    (const uint8_t*)CFDataGetBytePtr(cmapTable);
+                uint32_t cmapLen = CFDataGetLength(cmapTable);
+                nsRefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+                uint32_t offset;
+                nsresult rv;
+
+                rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset,
+                                            unicodeFont, symbolFont);
+                if (NS_SUCCEEDED(rv)) {
+                    fontData.mCharacterMap = charmap;
+                    fontData.mUVSOffset = offset;
+                    fontData.mSymbolFont = symbolFont;
+                    mLoadStats.cmaps++;
+                }
+                CFRelease(cmapTable);
+            }
+
+            mFontFaceData.Put(fontName, fontData);
+            CFRelease(faceName);
+        }
+
+        if (mLoadOtherNames && hasOtherFamilyNames) {
+            CFDataRef nameTable = CTFontCopyTable(fontRef, kCTFontTableName,
+                                                  kCTFontTableOptionNoOptions);
+            if (nameTable) {
+                const char *nameData = (const char*)CFDataGetBytePtr(nameTable);
+                uint32_t nameLen = CFDataGetLength(nameTable);
+                gfxFontFamily::ReadOtherFamilyNamesForFace(aFamilyName,
+                                                           nameData, nameLen,
+                                                           otherFamilyNames,
+                                                           false);
+                hasOtherFamilyNames = otherFamilyNames.Length() != 0;
+                CFRelease(nameTable);
+            }
+        }
+
+        CFRelease(fontRef);
+    }
+    CFRelease(matchingFonts);
+
+    // if found other names, insert them in the hash table
+    if (otherFamilyNames.Length() != 0) {
+        mOtherFamilyNames.Put(aFamilyName, otherFamilyNames);
+        mLoadStats.othernames += otherFamilyNames.Length();
+    }
+}
+
+already_AddRefed<FontInfoData>
+gfxMacPlatformFontList::CreateFontInfoData()
+{
+    bool loadCmaps = !UsesSystemFallback() ||
+        gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+
+    nsRefPtr<MacFontInfo> fi =
+        new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps);
+    return fi.forget();
+}
+
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -553,44 +553,63 @@ static void LogRegistryEvent(const wchar
   wsprintfW(buf, L" log %s", msg);
   hr = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buf, 0, KEY_READ, &dummyKey);
   if (SUCCEEDED(hr)) {
     RegCloseKey(dummyKey);
   }
 }
 #endif
 
+gfxFontFamily*
+gfxPlatformFontList::CheckFamily(gfxFontFamily *aFamily)
+{
+    if (aFamily && !aFamily->HasStyles()) {
+        aFamily->FindStyleVariations();
+        aFamily->CheckForSimpleFamily();
+    }
+
+    if (aFamily && aFamily->GetFontList().Length() == 0) {
+        // failed to load any faces for this family, so discard it
+        nsAutoString key;
+        GenerateFontListKey(aFamily->Name(), key);
+        mFontFamilies.Remove(key);
+        return nullptr;
+    }
+
+    return aFamily;
+}
+
 gfxFontFamily* 
 gfxPlatformFontList::FindFamily(const nsAString& aFamily)
 {
     nsAutoString key;
     gfxFontFamily *familyEntry;
     GenerateFontListKey(aFamily, key);
 
     NS_ASSERTION(mFontFamilies.Count() != 0, "system font list was not initialized correctly");
 
     // lookup in canonical (i.e. English) family name list
     if ((familyEntry = mFontFamilies.GetWeak(key))) {
-        return familyEntry;
+        return CheckFamily(familyEntry);
     }
 
     // lookup in other family names list (mostly localized names)
     if ((familyEntry = mOtherFamilyNames.GetWeak(key)) != nullptr) {
-        return familyEntry;
+        return CheckFamily(familyEntry);
     }
 
     // name not found and other family names not yet fully initialized so
     // initialize the rest of the list and try again.  this is done lazily
     // since reading name table entries is expensive.
     // although ASCII localized family names are possible they don't occur
     // in practice so avoid pulling in names at startup
     if (!mOtherFamilyNamesInitialized && !IsASCII(aFamily)) {
         InitOtherFamilyNames();
         if ((familyEntry = mOtherFamilyNames.GetWeak(key)) != nullptr) {
-            return familyEntry;
+            return CheckFamily(familyEntry);
         }
     }
 
     return nullptr;
 }
 
 gfxFontEntry*
 gfxPlatformFontList::FindFontForFamily(const nsAString& aFamily, const gfxFontStyle* aStyle, bool& aNeedsBold)
@@ -700,78 +719,115 @@ gfxPlatformFontList::RemoveCmap(const gf
     // cmap needs to match the entry *and* be the same ptr before removing
     CharMapHashKey *found =
         mSharedCmaps.GetEntry(const_cast<gfxCharacterMap*>(aCharMap));
     if (found && found->GetKey() == aCharMap) {
         mSharedCmaps.RemoveEntry(const_cast<gfxCharacterMap*>(aCharMap));
     }
 }
 
+static PLDHashOperator AppendFamilyToList(nsStringHashKey::KeyType aKey,
+                                          nsRefPtr<gfxFontFamily>& aFamilyEntry,
+                                          void *aUserArg)
+{
+    nsTArray<nsString> *familyNames = static_cast<nsTArray<nsString> *>(aUserArg);
+    familyNames->AppendElement(aFamilyEntry->Name());
+    return PL_DHASH_NEXT;
+}
+
+void
+gfxPlatformFontList::GetFontFamilyNames(nsTArray<nsString>& aFontFamilyNames)
+{
+    mFontFamilies.Enumerate(AppendFamilyToList, &aFontFamilyNames);
+}
+
 void 
 gfxPlatformFontList::InitLoader()
 {
-    GetFontFamilyList(mFontFamiliesToLoad);
+    GetFontFamilyNames(mFontInfo->mFontFamiliesToLoad);
     mStartIndex = 0;
-    mNumFamilies = mFontFamiliesToLoad.Length();
+    mNumFamilies = mFontInfo->mFontFamiliesToLoad.Length();
+    memset(&(mFontInfo->mLoadStats), 0, sizeof(mFontInfo->mLoadStats));
 }
 
 #define FONT_LOADER_MAX_TIMESLICE 100  // max time for one pass through RunLoader = 100ms
 
 bool
-gfxPlatformFontList::RunLoader()
+gfxPlatformFontList::LoadFontInfo()
 {
     TimeStamp start = TimeStamp::Now();
-    uint32_t i, endIndex = (mStartIndex + mIncrement < mNumFamilies ? mStartIndex + mIncrement : mNumFamilies);
+    uint32_t i, endIndex = mNumFamilies;
     bool loadCmaps = !UsesSystemFallback() ||
         gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
 
     // for each font family, load in various font info
     for (i = mStartIndex; i < endIndex; i++) {
-        gfxFontFamily* familyEntry = mFontFamiliesToLoad[i];
+        nsAutoString key;
+        gfxFontFamily *familyEntry;
+        GenerateFontListKey(mFontInfo->mFontFamiliesToLoad[i], key);
 
-        // find all faces that are members of this family
-        familyEntry->FindStyleVariations();
-        if (familyEntry->GetFontList().Length() == 0) {
-            // failed to load any faces for this family, so discard it
-            nsAutoString key;
-            GenerateFontListKey(familyEntry->Name(), key);
-            mFontFamilies.Remove(key);
+        // lookup in canonical (i.e. English) family name list
+        if (!(familyEntry = mFontFamilies.GetWeak(key))) {
             continue;
         }
 
+        // read in face names
+        familyEntry->ReadFaceNames(this, NeedFullnamePostscriptNames(), mFontInfo);
+
         // load the cmaps if needed
         if (loadCmaps) {
-            familyEntry->ReadAllCMAPs();
+            familyEntry->ReadAllCMAPs(mFontInfo);
         }
 
-        // read in face names
-        familyEntry->ReadFaceNames(this, NeedFullnamePostscriptNames());
-
-        // check whether the family can be considered "simple" for style matching
-        familyEntry->CheckForSimpleFamily();
-
         // limit the time spent reading fonts in one pass
         TimeDuration elapsed = TimeStamp::Now() - start;
         if (elapsed.ToMilliseconds() > FONT_LOADER_MAX_TIMESLICE &&
                 i + 1 != endIndex) {
             endIndex = i + 1;
             break;
         }
     }
 
     mStartIndex = endIndex;
+    bool done = mStartIndex >= mNumFamilies;
 
-    return (mStartIndex >= mNumFamilies);
+#ifdef PR_LOGGING
+    if (LOG_FONTINIT_ENABLED()) {
+        TimeDuration elapsed = TimeStamp::Now() - start;
+        LOG_FONTINIT(("(fontinit) fontloader load pass %8.2f ms done %s\n",
+                      elapsed.ToMilliseconds(), (done ? "true" : "false")));
+    }
+#endif
+
+    return done;
 }
 
 void 
-gfxPlatformFontList::FinishLoader()
+gfxPlatformFontList::CleanupLoader()
 {
     mFontFamiliesToLoad.Clear();
     mNumFamilies = 0;
+
+#ifdef PR_LOGGING
+    if (LOG_FONTINIT_ENABLED() && mFontInfo) {
+        LOG_FONTINIT(("(fontinit) fontloader load thread took %8.2f ms "
+                      "%d families %d fonts %d cmaps "
+                      "%d facenames %d othernames",
+                      mLoadTime.ToMilliseconds(),
+                      mFontInfo->mLoadStats.families,
+                      mFontInfo->mLoadStats.fonts,
+                      mFontInfo->mLoadStats.cmaps,
+                      mFontInfo->mLoadStats.facenames,
+                      mFontInfo->mLoadStats.othernames));
+    }
+#endif
+
+    mOtherFamilyNamesInitialized = true;
+    mFaceNamesInitialized = true;
+    gfxFontInfoLoader::CleanupLoader();
 }
 
 void
 gfxPlatformFontList::GetPrefsAndStartLoader()
 {
     mIncrement =
         std::max(1u, Preferences::GetUint(FONT_LOADER_FAMILIES_PER_SLICE_PREF));
 
--- a/gfx/thebes/gfxPlatformFontList.h
+++ b/gfx/thebes/gfxPlatformFontList.h
@@ -6,16 +6,17 @@
 #ifndef GFXPLATFORMFONTLIST_H_
 #define GFXPLATFORMFONTLIST_H_
 
 #include "nsDataHashtable.h"
 #include "nsRefPtrHashtable.h"
 #include "nsTHashtable.h"
 
 #include "gfxFontUtils.h"
+#include "gfxFontInfoLoader.h"
 #include "gfxFont.h"
 #include "gfxPlatform.h"
 
 #include "nsIMemoryReporter.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/MemoryReporting.h"
 
 class CharMapHashKey : public PLDHashEntryHdr
@@ -76,17 +77,17 @@ protected:
 struct FontListSizes {
     uint32_t mFontListSize; // size of the font list and dependent objects
                             // (font family and face names, etc), but NOT
                             // including the font table cache and the cmaps
     uint32_t mFontTableCacheSize; // memory used for the gfxFontEntry table caches
     uint32_t mCharMapsSize; // memory used for cmap coverage info
 };
 
-class gfxPlatformFontList : protected gfxFontInfoLoader
+class gfxPlatformFontList : public gfxFontInfoLoader
 {
 public:
     static gfxPlatformFontList* PlatformFontList() {
         return sPlatformFontList;
     }
 
     static nsresult Init() {
         NS_ASSERTION(!sPlatformFontList, "What's this doing here?");
@@ -205,16 +206,19 @@ protected:
                                              const gfxFontStyle* aMatchStyle,
                                              uint32_t& aCmapCount,
                                              gfxFontFamily** aMatchedFamily);
 
     // whether system-based font fallback is used or not
     // if system fallback is used, no need to load all cmaps
     virtual bool UsesSystemFallback() { return false; }
 
+    // verifies that a family contains a non-zero font count
+    gfxFontFamily* CheckFamily(gfxFontFamily *aFamily);
+
     // separate initialization for reading in name tables, since this is expensive
     void InitOtherFamilyNames();
 
     static PLDHashOperator InitOtherFamilyNamesProc(nsStringHashKey::KeyType aKey,
                                                     nsRefPtr<gfxFontFamily>& aFamilyEntry,
                                                     void* userArg);
 
     // read in all fullname/Postscript names for all font faces
@@ -235,20 +239,22 @@ protected:
 
     void GenerateFontListKey(const nsAString& aKeyName, nsAString& aResult);
 
     static PLDHashOperator
         HashEnumFuncForFamilies(nsStringHashKey::KeyType aKey,
                                 nsRefPtr<gfxFontFamily>& aFamilyEntry,
                                 void* aUserArg);
 
+    virtual void GetFontFamilyNames(nsTArray<nsString>& aFontFamilyNames);
+
     // gfxFontInfoLoader overrides, used to load in font cmaps
     virtual void InitLoader();
-    virtual bool RunLoader();
-    virtual void FinishLoader();
+    virtual bool LoadFontInfo();
+    virtual void CleanupLoader();
 
     // read the loader initialization prefs, and start it
     void GetPrefsAndStartLoader();
 
     // used by memory reporter to accumulate sizes of family names in the hash
     static size_t
     SizeOfFamilyNameEntryExcludingThis(const nsAString&               aKey,
                                        const nsRefPtr<gfxFontFamily>& aFamily,
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -15,16 +15,17 @@ EXPORTS += [
     'gfxCachedTempSurface.h',
     'gfxColor.h',
     'gfxContext.h',
     'gfxDrawable.h',
     'gfxFailure.h',
     'gfxFont.h',
     'gfxFontConstants.h',
     'gfxFontFeatures.h',
+    'gfxFontInfoLoader.h',
     'gfxFontTest.h',
     'gfxFontUtils.h',
     'gfxGradientCache.h',
     'gfxImageSurface.h',
     'gfxLineSegment.h',
     'gfxMatrix.h',
     'gfxPath.h',
     'gfxPattern.h',
@@ -239,16 +240,17 @@ SOURCES += [
 UNIFIED_SOURCES += [
     'gfx3DMatrix.cpp',
     'gfxAlphaRecovery.cpp',
     'gfxBaseSharedMemorySurface.cpp',
     'gfxBlur.cpp',
     'gfxCachedTempSurface.cpp',
     'gfxContext.cpp',
     'gfxFontFeatures.cpp',
+    'gfxFontInfoLoader.cpp',
     'gfxFontMissingGlyphs.cpp',
     'gfxFontTest.cpp',
     'gfxGradientCache.cpp',
     'gfxGraphiteShaper.cpp',
     'gfxHarfBuzzShaper.cpp',
     'gfxImageSurface.cpp',
     'gfxMatrix.cpp',
     'gfxPath.cpp',
--- a/js/jsd/jsd_val.cpp
+++ b/js/jsd/jsd_val.cpp
@@ -288,30 +288,30 @@ jsd_DropValue(JSDContext* jsdc, JSDValue
         free(jsdval);
     }
 }
 
 jsval
 jsd_GetValueWrappedJSVal(JSDContext* jsdc, JSDValue* jsdval)
 {
     AutoSafeJSContext cx;
-    JS::RootedObject obj(cx);
     JS::RootedValue val(cx, jsdval->val);
-    if (!JSVAL_IS_PRIMITIVE(val)) {
-        JSAutoCompartment ac(cx, JSVAL_TO_OBJECT(val));
-        obj = JS_ObjectToOuterObject(cx, JSVAL_TO_OBJECT(val));
+    if (!val.isPrimitive()) {
+        JS::RootedObject obj(cx, &val.toObject());
+        JSAutoCompartment ac(cx, obj);
+        obj = JS_ObjectToOuterObject(cx, obj);
         if (!obj)
         {
             JS_ClearPendingException(cx);
             val = JSVAL_NULL;
         }
         else
-            val = OBJECT_TO_JSVAL(obj);
+            val = JS::ObjectValue(*obj);
     }
-    
+
     return val;
 }
 
 static JSDProperty* _newProperty(JSDContext* jsdc, JS::HandleValue propId,
                                  JS::HandleValue propValue, JS::HandleValue propAlias,
                                  uint8_t propFlags, unsigned additionalFlags)
 {
     JSDProperty* jsdprop;
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -22,16 +22,17 @@
 using namespace js;
 
 using mozilla::DoubleIsInt32;
 using mozilla::Forward;
 using mozilla::IsNaN;
 using mozilla::Move;
 using mozilla::ArrayLength;
 using JS::DoubleNaNValue;
+using JS::ForOfIterator;
 
 
 /*** OrderedHashTable ****************************************************************************/
 
 /*
  * Define two collection templates, js::OrderedHashMap and js::OrderedHashSet.
  * They are like js::HashMap and js::HashSet except that:
  *
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -1048,17 +1048,17 @@ static JSObject*
 InitInt64Class(JSContext* cx,
                HandleObject parent,
                const JSClass* clasp,
                JSNative construct,
                const JSFunctionSpec* fs,
                const JSFunctionSpec* static_fs)
 {
   // Init type class and constructor
-  RootedObject prototype(cx, JS_InitClass(cx, parent, nullptr, clasp, construct,
+  RootedObject prototype(cx, JS_InitClass(cx, parent, js::NullPtr(), clasp, construct,
                                           0, nullptr, fs, nullptr, static_fs));
   if (!prototype)
     return nullptr;
 
   RootedObject ctor(cx, JS_GetConstructor(cx, prototype));
   if (!ctor)
     return nullptr;
   if (!JS_FreezeObject(cx, ctor))
@@ -5177,17 +5177,17 @@ StructType::FieldsArrayGetter(JSContext*
     if (!fields)
       return false;
     JS_SetReservedSlot(obj, SLOT_FIELDS, OBJECT_TO_JSVAL(fields));
 
     args.rval().setObject(*fields);
   }
 
   MOZ_ASSERT(args.rval().isObject());
-  MOZ_ASSERT(JS_IsArrayObject(cx, &args.rval().toObject()));
+  MOZ_ASSERT(JS_IsArrayObject(cx, args.rval()));
   return true;
 }
 
 bool
 StructType::FieldGetter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp)
 {
   if (!CData::IsCData(obj)) {
     JS_ReportError(cx, "not a CData");
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -149,16 +149,19 @@ CheckMarkedThing(JSTracer *trc, T *thing
     JS_ASSERT(MapTypeToTraceKind<T>::kind == GetGCThingTraceKind(thing));
 
     JS_ASSERT_IF(rt->gcStrictCompartmentChecking,
                  thing->zone()->isCollecting() || rt->isAtomsZone(thing->zone()));
 
     JS_ASSERT_IF(IS_GC_MARKING_TRACER(trc) && AsGCMarker(trc)->getMarkColor() == GRAY,
                  !thing->zone()->isGCMarkingBlack() || rt->isAtomsZone(thing->zone()));
 
+    JS_ASSERT_IF(IS_GC_MARKING_TRACER(trc),
+                 !(thing->zone()->isGCSweeping() || thing->zone()->isGCFinished()));
+
     /*
      * Try to assert that the thing is allocated.  This is complicated by the
      * fact that allocated things may still contain the poison pattern if that
      * part has not been overwritten, and that the free span list head in the
      * ArenaHeader may not be synced with the real one in ArenaLists.
      */
     JS_ASSERT_IF(IsThingPoisoned(thing) && rt->isHeapBusy(),
                  !InFreeList(thing->arenaHeader(), thing));
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -585,23 +585,25 @@ js::Nursery::moveElementsToTenured(JSObj
     if (!isInside(srcHeader)) {
         JS_ASSERT(src->elements == dst->elements);
         hugeSlots.remove(reinterpret_cast<HeapSlot*>(srcHeader));
         return 0;
     }
 
     /* ArrayBuffer stores byte-length, not Value count. */
     if (src->is<ArrayBufferObject>()) {
-        size_t nbytes = sizeof(ObjectElements) + srcHeader->initializedLength;
+        size_t nbytes;
         if (src->hasDynamicElements()) {
+            nbytes = sizeof(ObjectElements) + srcHeader->initializedLength;
             dstHeader = static_cast<ObjectElements *>(zone->malloc_(nbytes));
             if (!dstHeader)
                 CrashAtUnhandlableOOM("Failed to allocate array buffer elements while tenuring.");
         } else {
             dst->setFixedElements();
+            nbytes = GetGCKindSlots(dst->tenuredGetAllocKind()) * sizeof(HeapSlot);
             dstHeader = dst->getElementsHeader();
         }
         js_memcpy(dstHeader, srcHeader, nbytes);
         setElementsForwardingPointer(srcHeader, dstHeader, nbytes / sizeof(HeapSlot));
         dst->elements = dstHeader->elements();
         return src->hasDynamicElements() ? nbytes : 0;
     }
 
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -132,30 +132,38 @@ Zone::sweepBreakpoints(FreeOp *fop)
     /*
      * Sweep all compartments in a zone at the same time, since there is no way
      * to iterate over the scripts belonging to a single compartment in a zone.
      */
 
     gcstats::AutoPhase ap1(fop->runtime()->gcStats, gcstats::PHASE_SWEEP_TABLES);
     gcstats::AutoPhase ap2(fop->runtime()->gcStats, gcstats::PHASE_SWEEP_TABLES_BREAKPOINT);
 
+    JS_ASSERT(isGCSweeping());
     for (CellIterUnderGC i(this, FINALIZE_SCRIPT); !i.done(); i.next()) {
         JSScript *script = i.get<JSScript>();
+        JS_ASSERT(script->zone()->isGCSweeping());
         if (!script->hasAnyBreakpointsOrStepMode())
             continue;
+
         bool scriptGone = IsScriptAboutToBeFinalized(&script);
         JS_ASSERT(script == i.get<JSScript>());
         for (unsigned i = 0; i < script->length(); i++) {
             BreakpointSite *site = script->getBreakpointSite(script->offsetToPC(i));
             if (!site)
                 continue;
+
             Breakpoint *nextbp;
             for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) {
                 nextbp = bp->nextInSite();
-                if (scriptGone || IsObjectAboutToBeFinalized(&bp->debugger->toJSObjectRef()))
+                HeapPtrObject& dbgobj = bp->debugger->toJSObjectRef();
+                JS_ASSERT(dbgobj->zone()->isGCSweeping());
+                bool dying = scriptGone || IsObjectAboutToBeFinalized(&dbgobj);
+                JS_ASSERT_IF(!dying, bp->getHandler()->isMarked());
+                if (dying)
                     bp->destroy(fop);
             }
         }
     }
 }
 
 void
 Zone::discardJitCode(FreeOp *fop)
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug-956324.js
@@ -0,0 +1,28 @@
+var g = newGlobal();
+g.eval("function f() {\n" +
+       "    debugger;\n" +
+       "}\n")
+
+var dbg = new Debugger(g);
+var handler = {};
+dbg.onDebuggerStatement = function (frame) {
+  frame.script.setBreakpoint(0, {});
+};
+
+// create breakpoint
+g.f()
+
+// drop our references to things
+handler = undefined;
+dbg.onDebuggerStatement = undefined;
+
+dbg.removeAllDebuggees();
+
+gc();
+
+//create garbage to trigger a minor GC
+var x;
+for (var i = 0; i < 100; ++i)
+    x = {};
+
+gc();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug-961877.js
@@ -0,0 +1,14 @@
+g = Function("", "for (var i = 0; i < 0; ++i) { eval('this.arg'+0 +'=arg'+0); }");
+Math.abs(undefined);
+gczeal(2,300);
+evaluate("\
+var toFloat32 = (function() {\
+    var f32 = new Float32Array(1);\
+    function f(x) f32[0] = x;\
+    return f;\
+})();\
+for (var i = 0; i < 64; ++i) {\
+    var p = Math.pow(2, i) + 1;\
+    g(toFloat32(p));\
+    toFloat32(-p);\
+}");
--- a/js/src/jsapi-tests/testClassGetter.cpp
+++ b/js/src/jsapi-tests/testClassGetter.cpp
@@ -51,17 +51,17 @@ static bool test_fn(JSContext *cx, unsig
 
 static const JSFunctionSpec ptestFunctions[] = {
     JS_FS( "test_fn", test_fn, 0, 0 ),
     JS_FS_END
 };
 
 BEGIN_TEST(testClassGetter_isCalled)
 {
-    CHECK(JS_InitClass(cx, global, nullptr, &ptestClass, PTest, 0,
+    CHECK(JS_InitClass(cx, global, js::NullPtr(), &ptestClass, PTest, 0,
                        nullptr, ptestFunctions, nullptr, nullptr));
 
     EXEC("function check() { var o = new PTest(); o.test_fn(); o.test_value1; o.test_value2; o.test_value1; }");
 
     for (int i = 1; i < 9; i++) {
         JS::RootedValue rval(cx);
         CHECK(JS_CallFunctionName(cx, global, "check", 0, nullptr, rval.address()));
         CHECK_SAME(INT_TO_JSVAL(called_test_fn), INT_TO_JSVAL(i));
--- a/js/src/jsapi-tests/testCustomIterator.cpp
+++ b/js/src/jsapi-tests/testCustomIterator.cpp
@@ -57,17 +57,17 @@ IterClassConstructor(JSContext *cx, unsi
     if (!obj)
         return false;
     JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
     return true;
 }
 
 BEGIN_TEST(testCustomIterator_bug612523)
 {
-    CHECK(JS_InitClass(cx, global, nullptr, Jsvalify(&HasCustomIterClass),
+    CHECK(JS_InitClass(cx, global, js::NullPtr(), Jsvalify(&HasCustomIterClass),
                        IterClassConstructor, 0, nullptr, nullptr, nullptr, nullptr));
 
     JS::RootedValue result(cx);
     EVAL("var o = new HasCustomIter(); \n"
          "var j = 0; \n"
          "for (var i in o) { ++j; }; \n"
          "j;", result.address());
 
--- a/js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
+++ b/js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
@@ -23,17 +23,17 @@ ObjectEmulatingUndefinedConstructor(JSCo
     if (!obj)
         return false;
     JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
     return true;
 }
 
 BEGIN_TEST(testObjectEmulatingUndefined_truthy)
 {
-    CHECK(JS_InitClass(cx, global, nullptr, &ObjectEmulatingUndefinedClass,
+    CHECK(JS_InitClass(cx, global, js::NullPtr(), &ObjectEmulatingUndefinedClass,
                        ObjectEmulatingUndefinedConstructor, 0,
                        nullptr, nullptr, nullptr, nullptr));
 
     JS::RootedValue result(cx);
 
     EVAL("if (new ObjectEmulatingUndefined()) true; else false;", result.address());
     CHECK_SAME(result, JSVAL_FALSE);
 
@@ -49,17 +49,17 @@ BEGIN_TEST(testObjectEmulatingUndefined_
     CHECK_SAME(result, JSVAL_TRUE);
 
     return true;
 }
 END_TEST(testObjectEmulatingUndefined_truthy)
 
 BEGIN_TEST(testObjectEmulatingUndefined_equal)
 {
-    CHECK(JS_InitClass(cx, global, nullptr, &ObjectEmulatingUndefinedClass,
+    CHECK(JS_InitClass(cx, global, js::NullPtr(), &ObjectEmulatingUndefinedClass,
                        ObjectEmulatingUndefinedConstructor, 0,
                        nullptr, nullptr, nullptr, nullptr));
 
     JS::RootedValue result(cx);
 
     EVAL("if (new ObjectEmulatingUndefined() == undefined) true; else false;", result.address());
     CHECK_SAME(result, JSVAL_TRUE);
 
--- a/js/src/jsapi-tests/testProfileStrings.cpp
+++ b/js/src/jsapi-tests/testProfileStrings.cpp
@@ -77,17 +77,17 @@ static const JSFunctionSpec ptestFunctio
     JS_FS_END
 };
 
 static JSObject*
 initialize(JSContext *cx)
 {
     js::SetRuntimeProfilingStack(cx->runtime(), pstack, &psize, 10);
     JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
-    return JS_InitClass(cx, global, nullptr, &ptestClass, Prof, 0,
+    return JS_InitClass(cx, global, js::NullPtr(), &ptestClass, Prof, 0,
                         nullptr, ptestFunctions, nullptr, nullptr);
 }
 
 BEGIN_TEST(testProfileStrings_isCalledWithInterpreter)
 {
     CHECK(initialize(cx));
 
     EXEC("function g() { var p = new Prof(); p.test_fn(); }");
--- a/js/src/jsapi-tests/testTypedArrays.cpp
+++ b/js/src/jsapi-tests/testTypedArrays.cpp
@@ -81,18 +81,18 @@ TestPlainTypedArray(JSContext *cx)
     *data = 13;
     RootedValue v(cx);
     CHECK(JS_GetElement(cx, array, 0, &v));
     CHECK_SAME(v, INT_TO_JSVAL(13));
 
     return true;
 }
 
-template<JSObject *CreateWithBuffer(JSContext *, JSObject *, uint32_t, int32_t),
-         JSObject *CreateFromArray(JSContext *, JSObject *),
+template<JSObject *CreateWithBuffer(JSContext *, JS::HandleObject, uint32_t, int32_t),
+         JSObject *CreateFromArray(JSContext *, JS::HandleObject),
          typename Element,
          Element *GetData(JSObject *)>
 bool
 TestArrayFromBuffer(JSContext *cx)
 {
     size_t elts = 8;
     size_t nbytes = elts * sizeof(Element);
     RootedObject buffer(cx, JS_NewArrayBuffer(cx, nbytes));
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2289,23 +2289,21 @@ JS_PUBLIC_API(bool)
 JS_ConvertStub(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp)
 {
     JS_ASSERT(type != JSTYPE_OBJECT && type != JSTYPE_FUNCTION);
     JS_ASSERT(obj);
     return DefaultValue(cx, obj, type, vp);
 }
 
 JS_PUBLIC_API(JSObject *)
-JS_InitClass(JSContext *cx, JSObject *objArg, JSObject *parent_protoArg,
+JS_InitClass(JSContext *cx, HandleObject obj, HandleObject parent_proto,
              const JSClass *clasp, JSNative constructor, unsigned nargs,
              const JSPropertySpec *ps, const JSFunctionSpec *fs,
              const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs)
 {
-    RootedObject obj(cx, objArg);
-    RootedObject parent_proto(cx, parent_protoArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj, parent_proto);
     return js_InitClass(cx, obj, parent_proto, Valueify(clasp), constructor,
                         nargs, ps, fs, static_ps, static_fs);
 }
 
 JS_PUBLIC_API(bool)
@@ -3814,24 +3812,32 @@ JS_NewArrayObject(JSContext *cx, int len
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     assertSameCompartment(cx, JSValueArray(vector, vector ? (uint32_t)length : 0));
     return NewDenseCopiedArray(cx, (uint32_t)length, vector);
 }
 
 JS_PUBLIC_API(bool)
-JS_IsArrayObject(JSContext *cx, JSObject *objArg)
-{
-    RootedObject obj(cx, objArg);
+JS_IsArrayObject(JSContext *cx, JS::HandleObject obj)
+{
     assertSameCompartment(cx, obj);
     return ObjectClassIs(obj, ESClass_Array, cx);
 }
 
 JS_PUBLIC_API(bool)
+JS_IsArrayObject(JSContext *cx, JS::HandleValue value)
+{
+    if (!value.isObject())
+        return false;
+    RootedObject obj(cx, &value.toObject());
+    return JS_IsArrayObject(cx, obj);
+}
+
+JS_PUBLIC_API(bool)
 JS_GetArrayLength(JSContext *cx, HandleObject obj, uint32_t *lengthp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
     return GetLengthProperty(cx, obj, lengthp);
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2521,17 +2521,17 @@ struct JSFunctionSpec {
 #define JS_FNINFO(name,call,info,nargs,flags)                                 \
     JS_FNSPEC(name, call, info, nargs, flags, nullptr)
 #define JS_SELF_HOSTED_FN(name,selfHostedName,nargs,flags)                    \
     JS_FNSPEC(name, nullptr, nullptr, nargs, flags, selfHostedName)
 #define JS_FNSPEC(name,call,info,nargs,flags,selfHostedName)                  \
     {name, {call, info}, nargs, flags, selfHostedName}
 
 extern JS_PUBLIC_API(JSObject *)
-JS_InitClass(JSContext *cx, JSObject *obj, JSObject *parent_proto,
+JS_InitClass(JSContext *cx, JS::HandleObject obj, JS::HandleObject parent_proto,
              const JSClass *clasp, JSNative constructor, unsigned nargs,
              const JSPropertySpec *ps, const JSFunctionSpec *fs,
              const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs);
 
 /*
  * Set up ctor.prototype = proto and proto.constructor = ctor with the
  * right property flags.
  */
@@ -3081,17 +3081,20 @@ JS_SetUCProperty(JSContext *cx, JS::Hand
 extern JS_PUBLIC_API(bool)
 JS_DeleteUCProperty2(JSContext *cx, JSObject *obj, const jschar *name, size_t namelen,
                      bool *succeeded);
 
 extern JS_PUBLIC_API(JSObject *)
 JS_NewArrayObject(JSContext *cx, int length, jsval *vector);
 
 extern JS_PUBLIC_API(bool)
-JS_IsArrayObject(JSContext *cx, JSObject *obj);
+JS_IsArrayObject(JSContext *cx, JS::HandleValue value);
+
+extern JS_PUBLIC_API(bool)
+JS_IsArrayObject(JSContext *cx, JS::HandleObject obj);
 
 extern JS_PUBLIC_API(bool)
 JS_GetArrayLength(JSContext *cx, JS::Handle<JSObject*> obj, uint32_t *lengthp);
 
 extern JS_PUBLIC_API(bool)
 JS_SetArrayLength(JSContext *cx, JS::Handle<JSObject*> obj, uint32_t length);
 
 extern JS_PUBLIC_API(bool)
@@ -4802,11 +4805,68 @@ struct AsmJSCacheOps
     OpenAsmJSCacheEntryForWriteOp openEntryForWrite;
     CloseAsmJSCacheEntryForWriteOp closeEntryForWrite;
     BuildIdOp buildId;
 };
 
 extern JS_PUBLIC_API(void)
 SetAsmJSCacheOps(JSRuntime *rt, const AsmJSCacheOps *callbacks);
 
+/*
+ * Convenience class for imitating a JS level for-of loop. Typical usage:
+ *
+ *     ForOfIterator it(cx);
+ *     if (!it.init(iterable))
+ *       return false;
+ *     RootedValue val(cx);
+ *     while (true) {
+ *       bool done;
+ *       if (!it.next(&val, &done))
+ *         return false;
+ *       if (done)
+ *         break;
+ *       if (!DoStuff(cx, val))
+ *         return false;
+ *     }
+ */
+class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) {
+  protected:
+    JSContext *cx_;
+    JS::RootedObject iterator;
+
+    ForOfIterator(const ForOfIterator &) MOZ_DELETE;
+    ForOfIterator &operator=(const ForOfIterator &) MOZ_DELETE;
+
+  public:
+    ForOfIterator(JSContext *cx) : cx_(cx), iterator(cx) { }
+
+    enum NonIterableBehavior {
+        ThrowOnNonIterable,
+        AllowNonIterable
+    };
+
+    /*
+     * Initialize the iterator.  If AllowNonIterable is passed then if iterable
+     * does not have a callable @@iterator init() will just return true instead
+     * of throwing.  Callers should then check valueIsIterable() before
+     * continuing with the iteration.
+     */
+    bool init(JS::HandleValue iterable,
+              NonIterableBehavior nonIterableBehavior = ThrowOnNonIterable);
+
+    /*
+     * Get the next value from the iterator.  If false *done is true
+     * after this call, do not examine val.
+     */
+    bool next(JS::MutableHandleValue val, bool *done);
+
+    /*
+     * If initialized with throwOnNonCallable = false, check whether
+     * the value is iterable.
+     */
+    bool valueIsIterable() const {
+        return iterator;
+    }
+};
+
 } /* namespace JS */
 
 #endif /* jsapi_h */
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -120,21 +120,21 @@ JS_SetAccumulateTelemetryCallback(JSRunt
 extern JS_FRIEND_API(JSPrincipals *)
 JS_GetCompartmentPrincipals(JSCompartment *compartment);
 
 extern JS_FRIEND_API(void)
 JS_SetCompartmentPrincipals(JSCompartment *compartment, JSPrincipals *principals);
 
 /* Safe to call with input obj == nullptr. Returns non-nullptr iff obj != nullptr. */
 extern JS_FRIEND_API(JSObject *)
-JS_ObjectToInnerObject(JSContext *cx, JSObject *obj);
+JS_ObjectToInnerObject(JSContext *cx, JS::HandleObject obj);
 
 /* Requires obj != nullptr. */
 extern JS_FRIEND_API(JSObject *)
-JS_ObjectToOuterObject(JSContext *cx, JSObject *obj);
+JS_ObjectToOuterObject(JSContext *cx, JS::HandleObject obj);
 
 extern JS_FRIEND_API(JSObject *)
 JS_CloneObject(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent);
 
 extern JS_FRIEND_API(JSString *)
 JS_BasicObjectToString(JSContext *cx, JS::HandleObject obj);
 
 extern JS_FRIEND_API(bool)
@@ -1015,66 +1015,66 @@ JS_NewFloat64Array(JSContext *cx, uint32
  * Create a new typed array and copy in values from the given object. The
  * object is used as if it were an array; that is, the new array (if
  * successfully created) will have length given by array.length, and its
  * elements will be those specified by array[0], array[1], and so on, after
  * conversion to the typed array element type.
  */
 
 extern JS_FRIEND_API(JSObject *)
-JS_NewInt8ArrayFromArray(JSContext *cx, JSObject *array);
+JS_NewInt8ArrayFromArray(JSContext *cx, JS::HandleObject array);
 extern JS_FRIEND_API(JSObject *)
-JS_NewUint8ArrayFromArray(JSContext *cx, JSObject *array);
+JS_NewUint8ArrayFromArray(JSContext *cx, JS::HandleObject array);
 extern JS_FRIEND_API(JSObject *)
-JS_NewUint8ClampedArrayFromArray(JSContext *cx, JSObject *array);
+JS_NewUint8ClampedArrayFromArray(JSContext *cx, JS::HandleObject array);
 extern JS_FRIEND_API(JSObject *)
-JS_NewInt16ArrayFromArray(JSContext *cx, JSObject *array);
+JS_NewInt16ArrayFromArray(JSContext *cx, JS::HandleObject array);
 extern JS_FRIEND_API(JSObject *)
-JS_NewUint16ArrayFromArray(JSContext *cx, JSObject *array);
+JS_NewUint16ArrayFromArray(JSContext *cx, JS::HandleObject array);
 extern JS_FRIEND_API(JSObject *)
-JS_NewInt32ArrayFromArray(JSContext *cx, JSObject *array);
+JS_NewInt32ArrayFromArray(JSContext *cx, JS::HandleObject array);
 extern JS_FRIEND_API(JSObject *)
-JS_NewUint32ArrayFromArray(JSContext *cx, JSObject *array);
+JS_NewUint32ArrayFromArray(JSContext *cx, JS::HandleObject array);
 extern JS_FRIEND_API(JSObject *)
-JS_NewFloat32ArrayFromArray(JSContext *cx, JSObject *array);
+JS_NewFloat32ArrayFromArray(JSContext *cx, JS::HandleObject array);
 extern JS_FRIEND_API(JSObject *)
-JS_NewFloat64ArrayFromArray(JSContext *cx, JSObject *array);
+JS_NewFloat64ArrayFromArray(JSContext *cx, JS::HandleObject array);
 
 /*
  * Create a new typed array using the given ArrayBuffer for storage.  The
  * length value is optional; if -1 is passed, enough elements to use up the
  * remainder of the byte array is used as the default value.
  */
 
 extern JS_FRIEND_API(JSObject *)
-JS_NewInt8ArrayWithBuffer(JSContext *cx, JSObject *arrayBuffer,
+JS_NewInt8ArrayWithBuffer(JSContext *cx, JS::HandleObject arrayBuffer,
                           uint32_t byteOffset, int32_t length);
 extern JS_FRIEND_API(JSObject *)
-JS_NewUint8ArrayWithBuffer(JSContext *cx, JSObject *arrayBuffer,
+JS_NewUint8ArrayWithBuffer(JSContext *cx, JS::HandleObject arrayBuffer,
                            uint32_t byteOffset, int32_t length);
 extern JS_FRIEND_API(JSObject *)
-JS_NewUint8ClampedArrayWithBuffer(JSContext *cx, JSObject *arrayBuffer,
+JS_NewUint8ClampedArrayWithBuffer(JSContext *cx, JS::HandleObject arrayBuffer,
                                   uint32_t byteOffset, int32_t length);
 extern JS_FRIEND_API(JSObject *)
-JS_NewInt16ArrayWithBuffer(JSContext *cx, JSObject *arrayBuffer,
+JS_NewInt16ArrayWithBuffer(JSContext *cx, JS::HandleObject arrayBuffer,
                            uint32_t byteOffset, int32_t length);
 extern JS_FRIEND_API(JSObject *)
-JS_NewUint16ArrayWithBuffer(JSContext *cx, JSObject *arrayBuffer,
+JS_NewUint16ArrayWithBuffer(JSContext *cx, JS::HandleObject arrayBuffer,
                             uint32_t byteOffset, int32_t length);
 extern JS_FRIEND_API(JSObject *)
-JS_NewInt32ArrayWithBuffer(JSContext *cx, JSObject *arrayBuffer,
+JS_NewInt32ArrayWithBuffer(JSContext *cx, JS::HandleObject arrayBuffer,
                            uint32_t byteOffset, int32_t length);
 extern JS_FRIEND_API(JSObject *)
-JS_NewUint32ArrayWithBuffer(JSContext *cx, JSObject *arrayBuffer,
+JS_NewUint32ArrayWithBuffer(JSContext *cx, JS::HandleObject arrayBuffer,
                             uint32_t byteOffset, int32_t length);
 extern JS_FRIEND_API(JSObject *)
-JS_NewFloat32ArrayWithBuffer(JSContext *cx, JSObject *arrayBuffer,
+JS_NewFloat32ArrayWithBuffer(JSContext *cx, JS::HandleObject arrayBuffer,
                              uint32_t byteOffset, int32_t length);
 extern JS_FRIEND_API(JSObject *)
-JS_NewFloat64ArrayWithBuffer(JSContext *cx, JSObject *arrayBuffer,
+JS_NewFloat64ArrayWithBuffer(JSContext *cx, JS::HandleObject arrayBuffer,
                              uint32_t byteOffset, int32_t length);
 
 /*
  * Create a new ArrayBuffer with the given byte length.
  */
 extern JS_FRIEND_API(JSObject *)
 JS_NewArrayBuffer(JSContext *cx, uint32_t nbytes);
 
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -34,16 +34,17 @@
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/Stack-inl.h"
 #include "vm/String-inl.h"
 
 using namespace js;
 using namespace js::gc;
+using JS::ForOfIterator;
 
 using mozilla::ArrayLength;
 #ifdef JS_MORE_DETERMINISTIC
 using mozilla::PodCopy;
 #endif
 using mozilla::PodZero;
 
 typedef Rooted<PropertyIteratorObject*> RootedPropertyIteratorObject;
@@ -1264,36 +1265,40 @@ const Class StopIterationObject::class_ 
     JS_ConvertStub,
     nullptr,                 /* finalize    */
     nullptr,                 /* call        */
     stopiter_hasInstance,
     nullptr                  /* construct   */
 };
 
 bool
-ForOfIterator::init(HandleValue iterable)
+ForOfIterator::init(HandleValue iterable, NonIterableBehavior nonIterableBehavior)
 {
+    JSContext *cx = cx_;
     RootedObject iterableObj(cx, ToObject(cx, iterable));
     if (!iterableObj)
         return false;
 
     // The iterator is the result of calling obj[@@iterator]().
     InvokeArgs args(cx);
     if (!args.init(0))
         return false;
     args.setThis(ObjectValue(*iterableObj));
 
     RootedValue callee(cx);
     if (!JSObject::getProperty(cx, iterableObj, iterableObj, cx->names().std_iterator, &callee))
         return false;
 
-    // Throw if obj[@@iterator] isn't callable. js::Invoke is about to check
-    // for this kind of error anyway, but it would throw an inscrutable
-    // error message about |method| rather than this nice one about |obj|.
+    // Throw if obj[@@iterator] isn't callable if we were asked to do so.
+    // js::Invoke is about to check for this kind of error anyway, but it would
+    // throw an inscrutable error message about |method| rather than this nice
+    // one about |obj|.
     if (!callee.isObject() || !callee.toObject().isCallable()) {
+        if (nonIterableBehavior == AllowNonIterable)
+            return true;
         char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, iterable, NullPtr());
         if (!bytes)
             return false;
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, bytes);
         js_free(bytes);
         return false;
     }
 
@@ -1308,16 +1313,17 @@ ForOfIterator::init(HandleValue iterable
     return true;
 }
 
 bool
 ForOfIterator::next(MutableHandleValue vp, bool *done)
 {
     JS_ASSERT(iterator);
 
+    JSContext *cx = cx_;
     RootedValue method(cx);
     if (!JSObject::getProperty(cx, iterator, iterator, cx->names().next, &method))
         return false;
 
     InvokeArgs args(cx);
     if (!args.init(1))
         return false;
     args.setCallee(method);
--- a/js/src/jsiter.h
+++ b/js/src/jsiter.h
@@ -214,50 +214,16 @@ extern bool
 js_IteratorNext(JSContext *cx, js::HandleObject iterobj, js::MutableHandleValue rval);
 
 extern bool
 js_ThrowStopIteration(JSContext *cx);
 
 namespace js {
 
 /*
- * Convenience class for imitating a JS level for-of loop. Typical usage:
- *
- *     ForOfIterator it(cx, iterable);
- *     while (it.next()) {
- *        if (!DoStuff(cx, it.value()))
- *            return false;
- *     }
- *     if (!it.close())
- *         return false;
- *
- * The final it.close() check is needed in order to check for cases where
- * any of the iterator operations fail.
- *
- * it.close() may be skipped only if something in the body of the loop fails
- * and the failure is allowed to propagate on cx, as in this example if DoStuff
- * fails. In that case, ForOfIterator's destructor does all necessary cleanup.
- */
-class ForOfIterator
-{
-  private:
-    JSContext *cx;
-    RootedObject iterator;
-
-    ForOfIterator(const ForOfIterator &) MOZ_DELETE;
-    ForOfIterator &operator=(const ForOfIterator &) MOZ_DELETE;
-
-  public:
-    ForOfIterator(JSContext *cx) : cx(cx), iterator(cx) { }
-
-    bool init(HandleValue iterable);
-    bool next(MutableHandleValue val, bool *done);
-};
-
-/*
  * Create an object of the form { value: VALUE, done: DONE }.
  * ES6 draft from 2013-09-05, section 25.4.3.4.
  */
 extern JSObject *
 CreateItrResultObject(JSContext *cx, js::HandleValue value, bool done);
 
 } /* namespace js */
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -84,30 +84,28 @@ const Class JSObject::class_ = {
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub
 };
 
 const Class* const js::ObjectClassPtr = &JSObject::class_;
 
 JS_FRIEND_API(JSObject *)
-JS_ObjectToInnerObject(JSContext *cx, JSObject *objArg)
-{
-    RootedObject obj(cx, objArg);
+JS_ObjectToInnerObject(JSContext *cx, HandleObject obj)
+{
     if (!obj) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INACTIVE);
         return nullptr;
     }
     return GetInnerObject(cx, obj);
 }
 
 JS_FRIEND_API(JSObject *)
-JS_ObjectToOuterObject(JSContext *cx, JSObject *obj_)
-{
-    Rooted<JSObject*> obj(cx, obj_);
+JS_ObjectToOuterObject(JSContext *cx, HandleObject obj)
+{
     assertSameCompartment(cx, obj);
     return GetOuterObject(cx, obj);
 }
 
 JSObject *
 js::NonNullObject(JSContext *cx, const Value &v)
 {
     if (v.isPrimitive()) {
--- a/js/src/perf/jsperf.cpp
+++ b/js/src/perf/jsperf.cpp
@@ -219,20 +219,21 @@ GetPM(JSContext* cx, JS::HandleValue val
     JS_ReportErrorNumber(cx, js_GetErrorMessage, 0, JSMSG_INCOMPATIBLE_PROTO,
                          pm_class.name, fname, JS_GetClass(obj)->name);
     return nullptr;
 }
 
 namespace JS {
 
 JSObject*
-RegisterPerfMeasurement(JSContext *cx, JSObject *global)
+RegisterPerfMeasurement(JSContext *cx, HandleObject globalArg)
 {
+    RootedObject global(cx, globalArg);
     RootedObject prototype(cx);
-    prototype = JS_InitClass(cx, global, nullptr /* parent */,
+    prototype = JS_InitClass(cx, global, js::NullPtr() /* parent */,
                              &pm_class, pm_construct, 1,
                              pm_props, pm_fns, 0, 0);
     if (!prototype)
         return 0;
 
     RootedObject ctor(cx);
     ctor = JS_GetConstructor(cx, prototype);
     if (!ctor)
--- a/js/src/perf/jsperf.h
+++ b/js/src/perf/jsperf.h
@@ -113,17 +113,17 @@ class JS_FRIEND_API(PerfMeasurement)
     static bool canMeasureSomething();
 };
 
 /* Inject a Javascript wrapper around the above C++ class into the
  * Javascript object passed as an argument (this will normally be a
  * global object).  The JS-visible API is identical to the C++ API.
  */
 extern JS_FRIEND_API(JSObject*)
-    RegisterPerfMeasurement(JSContext *cx, JSObject *global);
+    RegisterPerfMeasurement(JSContext *cx, JS::HandleObject global);
 
 /*
  * Given a Value which contains an instance of the aforementioned
  * wrapper class, extract the C++ object.  Returns nullptr if the
  * Value is not an instance of the wrapper.
  */
 extern JS_FRIEND_API(PerfMeasurement*)
     ExtractPerfMeasurement(Value wrapper);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5383,18 +5383,18 @@ NewGlobalObject(JSContext *cx, JS::Compa
             return nullptr;
 
         /* Initialize FakeDOMObject. */
         static const js::DOMCallbacks DOMcallbacks = {
             InstanceClassHasProtoAtDepth
         };
         SetDOMCallbacks(cx->runtime(), &DOMcallbacks);
 
-        RootedObject domProto(cx, JS_InitClass(cx, glob, nullptr, &dom_class, dom_constructor, 0,
-                                               dom_props, dom_methods, nullptr, nullptr));
+        RootedObject domProto(cx, JS_InitClass(cx, glob, js::NullPtr(), &dom_class, dom_constructor,
+                                               0, dom_props, dom_methods, nullptr, nullptr));
         if (!domProto)
             return nullptr;
 
         /* Initialize FakeDOMObject.prototype */
         InitDOMObject(domProto);
     }
 
     JS_FireOnNewGlobalObject(cx, glob);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -337,16 +337,17 @@ BreakpointSite::hasBreakpoint(Breakpoint
         if (p == bp)
             return true;
     return false;
 }
 
 Breakpoint::Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler)
     : debugger(debugger), site(site), handler(handler)
 {
+    JS_ASSERT(handler->compartment() == debugger->object->compartment());
     JS_APPEND_LINK(&debuggerLinks, &debugger->breakpoints);
     JS_APPEND_LINK(&siteLinks, &site->breakpoints);
 }
 
 Breakpoint *
 Breakpoint::fromDebuggerLinks(JSCList *links)
 {
     return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, debuggerLinks));
@@ -2299,16 +2300,25 @@ Debugger::removeDebuggeeGlobal(FreeOp *f
      * for sure, and possibly the compartment's debuggee set.
      */
     v->erase(p);
     if (debugEnum)
         debugEnum->removeFront();
     else
         debuggees.remove(global);
 
+    /* Remove all breakpoints for the debuggee. */
+    Breakpoint *nextbp;
+    for (Breakpoint *bp = firstBreakpoint(); bp; bp = nextbp) {
+        nextbp = bp->nextInDebugger();
+        if (bp->site->script->compartment() == global->compartment())
+            bp->destroy(fop);
+    }
+    JS_ASSERT_IF(debuggees.empty(), !firstBreakpoint());
+
     /*
      * The debuggee needs to be removed from the compartment last, as this can
      * trigger GCs if the compartment's debug mode is being changed, and the
      * global cannot be rooted on the stack without a cx.
      */
     if (v->empty())
         global->compartment()->removeDebuggee(fop, global, invalidate, compartmentEnum);
 }
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -54,16 +54,17 @@
 
 using namespace js;
 using namespace js::gc;
 using namespace js::types;
 
 using mozilla::DebugOnly;
 using mozilla::DoubleEqualsInt32;
 using mozilla::PodCopy;
+using JS::ForOfIterator;
 
 /*
  * Note: when Clang 3.2 (32-bit) inlines the two functions below in Interpret,
  * the conservative stack scanner leaks a ton of memory and this negatively
  * influences performance. The MOZ_NEVER_INLINE is a temporary workaround until
  * we can remove the conservative scanner. See bug 849526 for more info.
  */
 #if defined(__clang__) && defined(JS_CPU_X86)
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -3524,39 +3524,36 @@ const JSFunctionSpec _typedArray##Object
 const JSFunctionSpec _typedArray##Object::jsfuncs[] = {                            \
     JS_SELF_HOSTED_FN("@@iterator", "ArrayValues", 0, 0),                          \
     JS_FN("subarray", _typedArray##Object::fun_subarray, 2, JSFUN_GENERIC_NATIVE), \
     JS_FN("set", _typedArray##Object::fun_set, 2, JSFUN_GENERIC_NATIVE),           \
     JS_FS_END                                                                      \
 }
 #endif
 
-#define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Name,NativeType)                                 \
-  JS_FRIEND_API(JSObject *) JS_New ## Name ## Array(JSContext *cx, uint32_t nelements)       \
-  {                                                                                          \
-      return TypedArrayObjectTemplate<NativeType>::fromLength(cx, nelements);                \
-  }                                                                                          \
-  JS_FRIEND_API(JSObject *) JS_New ## Name ## ArrayFromArray(JSContext *cx, JSObject *other_)\
-  {                                                                                          \
-      Rooted<JSObject*> other(cx, other_);                                                   \
-      return TypedArrayObjectTemplate<NativeType>::fromArray(cx, other);                     \
-  }                                                                                          \
-  JS_FRIEND_API(JSObject *) JS_New ## Name ## ArrayWithBuffer(JSContext *cx,                 \
-                               JSObject *arrayBuffer_, uint32_t byteOffset, int32_t length)  \
-  {                                                                                          \
-      Rooted<JSObject*> arrayBuffer(cx, arrayBuffer_);                                       \
-      Rooted<JSObject*> proto(cx, nullptr);                                                  \
-      return TypedArrayObjectTemplate<NativeType>::fromBuffer(cx, arrayBuffer, byteOffset,   \
-                                                              length, proto);                \
-  }                                                                                          \
-  JS_FRIEND_API(bool) JS_Is ## Name ## Array(JSObject *obj)                                  \
-  {                                                                                          \
-      if (!(obj = CheckedUnwrap(obj)))                                                       \
-          return false;                                                                      \
-      const Class *clasp = obj->getClass();                                                  \
+#define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Name,NativeType)                                    \
+  JS_FRIEND_API(JSObject *) JS_New ## Name ## Array(JSContext *cx, uint32_t nelements)          \
+  {                                                                                             \
+      return TypedArrayObjectTemplate<NativeType>::fromLength(cx, nelements);                   \
+  }                                                                                             \
+  JS_FRIEND_API(JSObject *) JS_New ## Name ## ArrayFromArray(JSContext *cx, HandleObject other) \
+  {                                                                                             \
+      return TypedArrayObjectTemplate<NativeType>::fromArray(cx, other);                        \
+  }                                                                                             \
+  JS_FRIEND_API(JSObject *) JS_New ## Name ## ArrayWithBuffer(JSContext *cx,                    \
+                               HandleObject arrayBuffer, uint32_t byteOffset, int32_t length)   \
+  {                                                                                             \
+      return TypedArrayObjectTemplate<NativeType>::fromBuffer(cx, arrayBuffer, byteOffset,      \
+                                                              length, js::NullPtr());           \
+  }                                                                                             \
+  JS_FRIEND_API(bool) JS_Is ## Name ## Array(JSObject *obj)                                     \
+  {                                                                                             \
+      if (!(obj = CheckedUnwrap(obj)))                                                          \
+          return false;                                                                         \
+      const Class *clasp = obj->getClass();                                                     \
       return (clasp == &TypedArrayObject::classes[TypedArrayObjectTemplate<NativeType>::ArrayTypeID()]); \
   }
 
 IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Int8, int8_t)
 IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Uint8, uint8_t)
 IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Uint8Clamped, uint8_clamped)
 IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Int16, int16_t)
 IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Uint16, uint16_t)
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -1266,18 +1266,17 @@ mozJSComponentLoader::ImportInto(const n
 
         RootedValue symbols(mContext);
         if (!JS_GetProperty(mContext, mod->obj,
                             "EXPORTED_SYMBOLS", &symbols)) {
             return ReportOnCaller(cxhelper, ERROR_NOT_PRESENT,
                                   PromiseFlatCString(aLocation).get());
         }
 
-        if (!symbols.isObject() ||
-            !JS_IsArrayObject(mContext, &symbols.toObject())) {
+        if (!JS_IsArrayObject(mContext, symbols)) {
             return ReportOnCaller(cxhelper, ERROR_NOT_AN_ARRAY,
                                   PromiseFlatCString(aLocation).get());
         }
 
         RootedObject symbolsObj(mContext, &symbols.toObject());
 
         // Iterate over symbols array, installing symbols on targetObj:
 
--- a/js/xpconnect/src/XPCCallContext.cpp
+++ b/js/xpconnect/src/XPCCallContext.cpp
@@ -350,17 +350,20 @@ XPCCallContext::UnwrapThisIfAllowed(Hand
     // property name. So we cheat a little bit here - we verify that the object
     // does indeed implement the method's Interface, and then just check that we
     // can successfully access property with method's name from the object.
 
     // First, get the XPCWN out of the underlying object. We should have a wrapper
     // here, potentially an outer window proxy, and then an XPCWN.
     MOZ_ASSERT(js::IsWrapper(obj));
     RootedObject unwrapped(mJSContext, js::UncheckedUnwrap(obj, /* stopAtOuter = */ false));
-    MOZ_ASSERT(unwrapped == JS_ObjectToInnerObject(mJSContext, js::Wrapper::wrappedObject(obj)));
+#ifdef DEBUG
+    JS::Rooted<JSObject*> wrappedObj(mJSContext, js::Wrapper::wrappedObject(obj));
+    MOZ_ASSERT(unwrapped == JS_ObjectToInnerObject(mJSContext, wrappedObj));
+#endif
 
     // Make sure we have an XPCWN, and grab it.
     if (!IS_WN_REFLECTOR(unwrapped))
         return nullptr;
     XPCWrappedNative *wn = XPCWrappedNative::Get(unwrapped);
 
     // Next, get the call info off the function object.
     XPCNativeInterface *interface;
--- a/js/xpconnect/src/XPCVariant.cpp
+++ b/js/xpconnect/src/XPCVariant.cpp
@@ -27,28 +27,29 @@ NS_IMPL_CI_INTERFACE_GETTER2(XPCVariant,
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(XPCVariant)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(XPCVariant)
 
 XPCVariant::XPCVariant(JSContext* cx, jsval aJSVal)
     : mJSVal(aJSVal), mCCGeneration(0)
 {
     nsVariant::Initialize(&mData);
-    if (!JSVAL_IS_PRIMITIVE(mJSVal)) {
+    if (!mJSVal.isPrimitive()) {
         // XXXbholley - The innerization here was from bug 638026. Blake says
         // the basic problem was that we were storing the C++ inner but the JS
         // outer, which meant that, after navigation, the JS inner could be
         // collected, which would cause us to try to recreate the JS inner at
         // some later point after teardown, which would crash. This is shouldn't
         // be a problem anymore because SetParentToWindow will do the right
         // thing, but I'm saving the cleanup here for another day. Blake thinks
         // that we should just not store the WN if we're creating a variant for
         // an outer window.
-        JSObject *obj = JS_ObjectToInnerObject(cx, JSVAL_TO_OBJECT(mJSVal));
-        mJSVal = OBJECT_TO_JSVAL(obj);
+        JS::RootedObject obj(cx, &mJSVal.toObject());
+        obj = JS_ObjectToInnerObject(cx, obj);
+        mJSVal = JS::ObjectValue(*obj);
 
         JSObject *unwrapped = js::CheckedUnwrap(obj, /* stopAtOuter = */ false);
         mReturnRawObject = !(unwrapped && IS_WN_REFLECTOR(unwrapped));
     } else
         mReturnRawObject = false;
 }
 
 XPCTraceableVariant::~XPCTraceableVariant()
--- a/layout/forms/test/mochitest.ini
+++ b/layout/forms/test/mochitest.ini
@@ -30,14 +30,15 @@ support-files =
 [test_bug595310.html]
 [test_bug620936.html]
 [test_bug644542.html]
 [test_bug672810.html]
 [test_bug704049.html]
 [test_bug717878_input_scroll.html]
 [test_bug869314.html]
 [test_bug903715.html]
+[test_bug935876.html]
 [test_bug957562.html]
 [test_bug960277.html]
 [test_listcontrol_search.html]
 [test_select_prevent_default.html]
 [test_textarea_resize.html]
 [test_bug961363.html]
new file mode 100644
--- /dev/null
+++ b/layout/forms/test/test_bug935876.html
@@ -0,0 +1,491 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=935876
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 935876</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=935876">Mozilla Bug 935876</a>
+<p id="display"></p>
+<div>
+<select id="listbox" size="3">
+  <option selected>1</option>
+  <option>2</option>
+  <option>3</option>
+  <option>4</option>
+  <option>5</option>
+  <option>6</option>
+  <option>7</option>
+</select>
+<select id="multipleListbox" size="3" multiple>
+  <option selected>1</option>
+  <option>2</option>
+  <option>3</option>
+  <option>4</option>
+  <option>5</option>
+  <option>6</option>
+  <option>7</option>
+</select>
+<select id="combobox">
+  <option selected>1</option>
+  <option>2</option>
+  <option>3</option>
+  <option>4</option>
+  <option>5</option>
+  <option>6</option>
+  <option>7</option>
+</select>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+const kIsAndroid = navigator.appVersion.indexOf("Android") != 0;
+
+function runTests()
+{
+  var doPreventDefault = false;
+  function onKeydown(aEvent)
+  {
+    if (doPreventDefault) {
+      aEvent.preventDefault();
+    }
+  }
+
+  var keyPressEventFired = false;
+  function onKeypress(aEvent)
+  {
+    keyPressEventFired = true;
+  }
+
+  var keyDownEventConsumedByJS = false;
+  var keyDownEventConsumed = false;
+  function onkeydownInSystemEventGroup(aEvent)
+  {
+    keyDownEventConsumedByJS = aEvent.defaultPrevented;
+    keyDownEventConsumed = aEvent.getPreventDefault();
+  }
+
+  function reset()
+  {
+    keyPressEventFired = false;
+    keyDownEventConsumedByJS = false;
+    keyDownEventConsumed = false;
+  }
+
+  function check(aExpectingKeydownConsumed, aDescription)
+  {
+    if (doPreventDefault) {
+      ok(!keyPressEventFired, "keypress event shouldn't be fired for " + aDescription +
+                              " if preventDefault() of keydown event was called");
+      ok(keyDownEventConsumedByJS, "keydown event of " + aDescription +
+                                   " should be consumed in content level if preventDefault() of keydown event is called");
+      ok(keyDownEventConsumed, "keydown event of " + aDescription +
+                               " should be consumed in system level if preventDefault() of keydown event is called");
+    } else if (aExpectingKeydownConsumed) {
+      ok(!keyPressEventFired, "keypress event shouldn't be fired for " + aDescription);
+      ok(!keyDownEventConsumedByJS, "keydown event of " + aDescription + " shouldn't be consumed in content level");
+      ok(keyDownEventConsumed, "keydown event of " + aDescription + " should be consumed in system level");
+    } else {
+      ok(keyPressEventFired, "keypress event should be fired for " + aDescription);
+      ok(!keyDownEventConsumedByJS, "keydown event of " + aDescription + " shouldn't be consumed in content level");
+      ok(!keyDownEventConsumed, "keydown event of " + aDescription + " should be consumed in system level");
+    }
+  }
+
+  var listbox = document.getElementById("listbox");
+  listbox.addEventListener("keydown", onKeydown, false);
+  listbox.addEventListener("keypress", onKeypress, false);
+  SpecialPowers.addSystemEventListener(listbox, "keydown", onkeydownInSystemEventGroup, false);
+
+  listbox.focus();
+
+  [ false, true ].forEach(function (consume) {
+    doPreventDefault = consume;
+    for (var i = 0; i < listbox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_DOWN", {});
+      check(true, "DownArrow key on listbox #" + i);
+    }
+
+    for (var i = 0; i < listbox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_UP", {});
+      check(true, "UpArrow key on listbox #" + i);
+    }
+
+    for (var i = 0; i < listbox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_RIGHT", {});
+      check(true, "RightArrow key on listbox #" + i);
+    }
+
+    for (var i = 0; i < listbox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_LEFT", {});
+      check(true, "LeftArrow key on listbox #" + i);
+    }
+
+    for (var i = 0; i < 4; i++) {
+      reset()
+      synthesizeKey("VK_PAGE_DOWN", {});
+      check(true, "PageDown key on listbox #" + i);
+    }
+
+    for (var i = 0; i < 4; i++) {
+      reset()
+      synthesizeKey("VK_PAGE_UP", {});
+      check(true, "PageUp key on listbox #" + i);
+    }
+
+    for (var i = 0; i < 2; i++) {
+      reset()
+      synthesizeKey("VK_END", {});
+      check(true, "End key on listbox #" + i);
+    }
+
+    for (var i = 0; i < 2; i++) {
+      reset()
+      synthesizeKey("VK_HOME", {});
+      check(true, "Home key on listbox #" + i);
+    }
+
+    reset()
+    synthesizeKey("VK_RETURN", {});
+    check(false, "Enter key on listbox");
+
+    reset()
+    synthesizeKey("VK_ESCAPE", {});
+    check(false, "Esc key on listbox");
+
+    reset()
+    synthesizeKey("VK_F4", {});
+    check(false, "F4 key on listbox");
+
+    reset()
+    synthesizeKey("a", {});
+    check(false, "'A' key on listbox");
+  });
+
+  listbox.removeEventListener("keydown", onKeydown, false);
+  listbox.removeEventListener("keypress", onKeypress, false);
+  SpecialPowers.removeSystemEventListener(listbox, "keydown", onkeydownInSystemEventGroup, false);
+
+
+
+  var multipleListbox = document.getElementById("multipleListbox");
+  multipleListbox.addEventListener("keydown", onKeydown, false);
+  multipleListbox.addEventListener("keypress", onKeypress, false);
+  SpecialPowers.addSystemEventListener(multipleListbox, "keydown", onkeydownInSystemEventGroup, false);
+
+  multipleListbox.focus();
+
+  [ false, true ].forEach(function (consume) {
+    doPreventDefault = consume;
+    for (var i = 0; i < multipleListbox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_DOWN", {});
+      check(true, "DownArrow key on multiple listbox #" + i);
+    }
+
+    for (var i = 0; i < multipleListbox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_UP", {});
+      check(true, "UpArrow key on multiple listbox #" + i);
+    }
+
+    for (var i = 0; i < multipleListbox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_RIGHT", {});
+      check(true, "RightArrow key on multiple listbox #" + i);
+    }
+
+    for (var i = 0; i < multipleListbox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_LEFT", {});
+      check(true, "LeftArrow key on multiple listbox #" + i);
+    }
+
+    for (var i = 0; i < 4; i++) {
+      reset()
+      synthesizeKey("VK_PAGE_DOWN", {});
+      check(true, "PageDown key on multiple listbox #" + i);
+    }
+
+    for (var i = 0; i < 4; i++) {
+      reset()
+      synthesizeKey("VK_PAGE_UP", {});
+      check(true, "PageUp key on multiple listbox #" + i);
+    }
+
+    for (var i = 0; i < 2; i++) {
+      reset()
+      synthesizeKey("VK_END", {});
+      check(true, "End key on multiple listbox #" + i);
+    }
+
+    for (var i = 0; i < 2; i++) {
+      reset()
+      synthesizeKey("VK_HOME", {});
+      check(true, "Home key on multiple listbox #" + i);
+    }
+
+    reset()
+    synthesizeKey("VK_RETURN", {});
+    check(true, "Enter key on multiple listbox");
+
+    reset()
+    synthesizeKey("VK_ESCAPE", {});
+    check(false, "Esc key on multiple listbox");
+
+    reset()
+    synthesizeKey("VK_F4", {});
+    check(false, "F4 key on multiple listbox");
+
+    reset()
+    synthesizeKey("a", {});
+    check(false, "'A' key on multiple listbox");
+  });
+
+  multipleListbox.removeEventListener("keydown", onKeydown, false);
+  multipleListbox.removeEventListener("keypress", onKeypress, false);
+  SpecialPowers.removeSystemEventListener(multipleListbox, "keydown", onkeydownInSystemEventGroup, false);
+
+
+
+  var combobox = document.getElementById("combobox");
+  combobox.addEventListener("keydown", onKeydown, false);
+  combobox.addEventListener("keypress", onKeypress, false);
+  SpecialPowers.addSystemEventListener(combobox, "keydown", onkeydownInSystemEventGroup, false);
+
+  combobox.focus();
+
+  [ false, true ].forEach(function (consume) {
+    doPreventDefault = consume;
+    for (var i = 0; i < combobox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_DOWN", {});
+      check(true, "DownArrow key on combobox #" + i);
+    }
+
+    for (var i = 0; i < combobox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_UP", {});
+      check(true, "UpArrow key on combobox #" + i);
+    }
+
+    for (var i = 0; i < combobox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_RIGHT", {});
+      check(true, "RightArrow key on combobox #" + i);
+    }
+
+    for (var i = 0; i < combobox.options.length + 1; i++) {
+      reset()
+      synthesizeKey("VK_LEFT", {});
+      check(true, "LeftArrow key on combobox #" + i);
+    }
+
+    for (var i = 0; i < 4; i++) {
+      reset()
+      synthesizeKey("VK_PAGE_DOWN", {});
+      check(true, "PageDown key on combobox #" + i);
+    }
+
+    for (var i = 0; i < 4; i++) {
+      reset()
+      synthesizeKey("VK_PAGE_UP", {});
+      check(true, "PageUp key on combobox #" + i);
+    }
+
+    for (var i = 0; i < 2; i++) {
+      reset()
+      synthesizeKey("VK_END", {});
+      check(true, "End key on combobox #" + i);
+    }
+
+    for (var i = 0; i < 2; i++) {
+      reset()
+      synthesizeKey("VK_HOME", {});
+      check(true, "Home key on combobox #" + i);
+    }
+
+    reset()
+    synthesizeKey("VK_RETURN", {});
+    check(true, "Enter key on combobox");
+
+    reset()
+    synthesizeKey("VK_ESCAPE", {});
+    check(true, "Esc key on combobox");
+
+    if (!kIsWin) {
+      reset()
+      synthesizeKey("VK_F4", {});
+      check(false, "F4 key on combobox");
+    }
+
+    reset()
+    synthesizeKey("a", {});
+    check(false, "'A' key on combobox");
+  });
+
+  function finish()
+  {
+    combobox.removeEventListener("keydown", onKeydown, false);
+    combobox.removeEventListener("keypress", onKeypress, false);
+    SpecialPowers.removeSystemEventListener(combobox, "keydown", onkeydownInSystemEventGroup, false);
+    SimpleTest.finish();
+  }
+
+  // Mac uses native popup for dropdown.  Let's skip the tests for popup
+  // since it's not handled in nsListControlFrame.
+  // Similarly, Android doesn't use popup for dropdown.
+  if (kIsMac || kIsAndroid) {
+    finish();
+    return;
+  }
+
+  function testDropDown(aCallback)
+  {
+    testOpenDropDown(function () {
+      reset()
+      synthesizeKey("VK_DOWN", { altKey: true });
+    }, function () {
+      check(true, "Alt + DownArrow key on combobox at opening dropdown");
+
+      for (var i = 0; i < combobox.options.length + 1; i++) {
+        reset()
+        synthesizeKey("VK_DOWN", {});
+        check(true, "DownArrow key on combobox during dropdown open #" + i);
+      }
+
+      for (var i = 0; i < combobox.options.length + 1; i++) {
+        reset()
+        synthesizeKey("VK_UP", {});
+        check(true, "UpArrow key on combobox during dropdown open #" + i);
+      }
+
+      for (var i = 0; i < combobox.options.length + 1; i++) {
+        reset()
+        synthesizeKey("VK_RIGHT", {});
+        check(true, "RightArrow key on combobox during dropdown open #" + i);
+      }
+
+      for (var i = 0; i < combobox.options.length + 1; i++) {
+        reset()
+        synthesizeKey("VK_LEFT", {});
+        check(true, "LeftArrow key on combobox during dropdown open #" + i);
+      }
+
+      for (var i = 0; i < 4; i++) {
+        reset()
+        synthesizeKey("VK_PAGE_DOWN", {});
+        check(true, "PageDown key on combobox during dropdown open #" + i);
+      }
+
+      for (var i = 0; i < 4; i++) {
+        reset()
+        synthesizeKey("VK_PAGE_UP", {});
+        check(true, "PageUp key on combobox during dropdown open #" + i);
+      }
+
+      for (var i = 0; i < 2; i++) {
+        reset()
+        synthesizeKey("VK_END", {});
+        check(true, "End key on combobox during dropdown open #" + i);
+      }
+
+      for (var i = 0; i < 2; i++) {
+        reset()
+        synthesizeKey("VK_HOME", {});
+        check(true, "Home key on combobox during dropdown open #" + i);
+      }
+
+      testCloseDropDown(function () {
+        reset()
+        synthesizeKey("VK_RETURN", {});
+      }, function () {
+        testOpenDropDown(function () {
+          check(true, "Enter key on combobox at closing dropdown");
+
+          synthesizeKey("VK_UP", { altKey: true });
+        }, function () {
+          check(true, "Alt + UpArrow key on combobox at opening dropdown");
+
+          testCloseDropDown(function () {
+            reset()
+            synthesizeKey("VK_ESCAPE", {});
+          }, function () {
+            check(true, "Esc key on combobox at closing dropdown");
+
+            // F4 key opens/closes dropdown only on Windows. So, other platforms
+            // don't need to do anymore.
+            if (!kIsWin) {
+              aCallback();
+              return;
+            }
+
+            testOpenDropDown(function () {
+              reset()
+              synthesizeKey("VK_F4", {});
+            }, function () {
+              check(true, "F4 key on combobox at opening dropdown on Windows");
+
+              testCloseDropDown(function () {
+                reset()
+                synthesizeKey("VK_F4", {});
+              }, function () {
+                check(true, "F4 key on combobox at closing dropdown on Windows");
+
+                aCallback();
+                return;
+              });
+            });
+          });
+        });
+      });
+    });
+  }
+
+  doPreventDefault = false;
+  testDropDown(function () {
+    // Even if keydown event is consumed by JS, opening/closing dropdown
+    // should work for a11y and security (e.g., cannot close dropdown causes
+    // staying top-most window on the screen).  If it's blocked by JS, this
+    // test would cause permanent timeout.
+    doPreventDefault = true;
+    testDropDown(finish);
+  });
+}
+
+function testOpenDropDown(aTest, aOnOpenDropDown)
+{
+  document.addEventListener("popupshowing", function (aEvent) {
+    document.removeEventListener(aEvent.type, arguments.callee, false);
+    setTimeout(aOnOpenDropDown, 0);
+  }, false);
+  aTest();
+}
+
+function testCloseDropDown(aTest, aOnCloseDropDown)
+{
+  document.addEventListener("popuphiding", function (aEvent) {
+    document.removeEventListener(aEvent.type, arguments.callee, false);
+    setTimeout(aOnCloseDropDown, 0)
+  }, false);
+  aTest();
+}
+
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
--- a/testing/mochitest/b2g-desktop.json
+++ b/testing/mochitest/b2g-desktop.json
@@ -385,16 +385,17 @@
     "layout/base/tests/test_bug629838.html":"depends on plugins support",
     "layout/base/tests/test_mozPaintCount.html":"depends on plugins support",
     "layout/forms/test/test_bug348236.html":"select form control popup",
     "layout/forms/test/test_bug446663.html":"needs copy support",
     "layout/forms/test/test_bug564115.html":"times out on window.open and focus event",
     "layout/forms/test/test_bug571352.html":"shift-click multi-select not working?",
     "layout/forms/test/test_textarea_resize.html":"resizing textarea not available in b2g",
     "layout/forms/test/test_bug903715.html":"select elements don't use an in-page popup in B2G",
+    "layout/forms/test/test_bug935876.html":"Esc key is consumed by another event handler only on desktop B2G",
     "layout/forms/test/test_listcontrol_search.html" : "select elements don't use an in-page popup in B2G",
     "layout/generic/test/test_bug392746.html":"ctrl mouse select not working in b2g",
     "layout/generic/test/test_bug470212.html":"shift mouse select not working in b2g",
     "layout/generic/test/test_bug514732.html":"times out, also on Android",
     "layout/generic/test/test_bug791616.html":"Target should not have scrolled - got 114.10000610351562, expected 115.39999389648438",
     "layout/generic/test/test_invalidate_during_plugin_paint.html":"plugins not supported",
     "layout/generic/test/test_page_scroll_with_fixed_pos.html":"opened window too small?",
     "layout/generic/test/test_plugin_focus.html":"plugins not supported",
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -408,33 +408,33 @@ GetIntFromJSObject(JSContext* aCtx,
  * @pre aArray must be an Array object.
  *
  * @param aCtx
  *        The JSContext for aArray.
  * @param aArray
  *        The JSObject to get the object from.
  * @param aIndex
  *        The index to get the object from.
- * @param _object
- *        The JSObject pointer on success.
+ * @param objOut
+ *        Set to the JSObject pointer on success.
  */
 nsresult
 GetJSObjectFromArray(JSContext* aCtx,
-                     JSObject* aArray,
+                     JS::Handle<JSObject*> aArray,
                      uint32_t aIndex,
-                     JSObject** _rooter)
+                     JS::MutableHandle<JSObject*> objOut)
 {
   NS_PRECONDITION(JS_IsArrayObject(aCtx, aArray),
                   "Must provide an object that is an array!");
 
   JS::Rooted<JS::Value> value(aCtx);
   bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
   NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
-  NS_ENSURE_ARG(!JSVAL_IS_PRIMITIVE(value));
-  *_rooter = JSVAL_TO_OBJECT(value);
+  NS_ENSURE_ARG(!value.isPrimitive());
+  objOut.set(&value.toObject());
   return NS_OK;
 }
 
 class VisitedQuery : public AsyncStatementCallback
 {
 public:
   static nsresult Start(nsIURI* aURI,
                         mozIVisitedStatusCallback* aCallback=nullptr)
@@ -2755,17 +2755,17 @@ History::UpdatePlaces(JS::Handle<JS::Val
   uint32_t infosLength;
   JS::Rooted<JSObject*> infos(aCtx);
   nsresult rv = GetJSArrayFromJSValue(aPlaceInfos, aCtx, &infos, &infosLength);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsTArray<VisitData> visitData;
   for (uint32_t i = 0; i < infosLength; i++) {
     JS::Rooted<JSObject*> info(aCtx);
-    nsresult rv = GetJSObjectFromArray(aCtx, infos, i, info.address());
+    nsresult rv = GetJSObjectFromArray(aCtx, infos, i, &info);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIURI> uri = GetURIFromJSObject(aCtx, info, "uri");
     nsCString guid;
     {
       nsString fatGUID;
       GetStringFromJSObject(aCtx, info, "guid", fatGUID);
       if (fatGUID.IsVoid()) {
@@ -2809,17 +2809,17 @@ History::UpdatePlaces(JS::Handle<JS::Val
       (void)JS_GetArrayLength(aCtx, visits, &visitsLength);
     }
     NS_ENSURE_ARG(visitsLength > 0);
 
     // Check each visit, and build our array of VisitData objects.
     visitData.SetCapacity(visitData.Length() + visitsLength);
     for (uint32_t j = 0; j < visitsLength; j++) {
       JS::Rooted<JSObject*> visit(aCtx);
-      rv = GetJSObjectFromArray(aCtx, visits, j, visit.address());
+      rv = GetJSObjectFromArray(aCtx, visits, j, &visit);
       NS_ENSURE_SUCCESS(rv, rv);
 
       VisitData& data = *visitData.AppendElement(VisitData(uri));
       data.title = title;
       data.guid = guid;
 
       // We must have a date and a transaction type!
       rv = GetIntFromJSObject(aCtx, visit, "visitDate", &data.visitTime);