Merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 16 Dec 2016 16:22:39 -0800
changeset 450590 119f24606f99d82c6539763a778bb24f9df28037
parent 450589 74f4edb0d09dbb7809eadaff9d321d61203bbaf1 (current diff)
parent 450516 5a6af42c741deb13656fb868bb2ce1c5f26108dd (diff)
child 450591 34a1ab064cb5b868fa75cb74d052e978eb34d6c1
child 458107 e713b6b10f071f7df9e975c9c293df0971eda1d0
push id38911
push userbmo:cku@mozilla.com
push dateSat, 17 Dec 2016 03:26:49 +0000
reviewersmerge
milestone53.0a1
Merge inbound to m-c a=merge MozReview-Commit-ID: 85xWi937vw6
--- a/accessible/base/EventTree.cpp
+++ b/accessible/base/EventTree.cpp
@@ -20,25 +20,24 @@ using namespace mozilla::a11y;
 ////////////////////////////////////////////////////////////////////////////////
 // TreeMutation class
 
 EventTree* const TreeMutation::kNoEventTree = reinterpret_cast<EventTree*>(-1);
 
 TreeMutation::TreeMutation(Accessible* aParent, bool aNoEvents) :
   mParent(aParent), mStartIdx(UINT32_MAX),
   mStateFlagsCopy(mParent->mStateFlags),
-  mEventTree(aNoEvents ? kNoEventTree : nullptr),
   mQueueEvents(!aNoEvents)
 {
 #ifdef DEBUG
   mIsDone = false;
 #endif
 
 #ifdef A11Y_LOG
-  if (mEventTree != kNoEventTree && logging::IsEnabled(logging::eEventTree)) {
+  if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
     logging::MsgBegin("EVENTS_TREE", "reordering tree before");
     logging::AccessibleInfo("reordering for", mParent);
     Controller()->RootEventTree().Log();
     logging::MsgEnd();
 
     if (logging::IsEnabled(logging::eVerbose)) {
       logging::Tree("EVENTS_TREE", "Container tree", mParent->Document(),
                     PrefixLog, static_cast<void*>(this));
@@ -114,17 +113,17 @@ TreeMutation::Done()
   mParent->mEmbeddedObjCollector = nullptr;
   mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating;
 
 #ifdef DEBUG
   mIsDone = true;
 #endif
 
 #ifdef A11Y_LOG
-  if (mEventTree != kNoEventTree && logging::IsEnabled(logging::eEventTree)) {
+  if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
     logging::MsgBegin("EVENTS_TREE", "reordering tree after");
     logging::AccessibleInfo("reordering for", mParent);
     Controller()->RootEventTree().Log();
     logging::MsgEnd();
   }
 #endif
 }
 
--- a/accessible/base/EventTree.h
+++ b/accessible/base/EventTree.h
@@ -42,17 +42,16 @@ private:
 
 #ifdef A11Y_LOG
   static const char* PrefixLog(void* aData, Accessible*);
 #endif
 
   Accessible* mParent;
   uint32_t mStartIdx;
   uint32_t mStateFlagsCopy;
-  EventTree* mEventTree;
 
   /*
    * True if mutation events should be queued.
    */
   bool mQueueEvents;
 
 #ifdef DEBUG
   bool mIsDone;
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -837,16 +837,20 @@ NotificationController::WillRefresh(mozi
   }
 
   ProcessEventQueue();
 
   if (IPCAccessibilityActive()) {
     size_t newDocCount = newChildDocs.Length();
     for (size_t i = 0; i < newDocCount; i++) {
       DocAccessible* childDoc = newChildDocs[i];
+      if (childDoc->IsDefunct()) {
+        continue;
+      }
+
       Accessible* parent = childDoc->Parent();
       DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
       uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
       MOZ_ASSERT(id);
       DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
       if (ipcDoc) {
         parentIPCDoc->SendBindChildDoc(ipcDoc, id);
         continue;
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -404,24 +404,30 @@ Sanitizer.prototype = {
     },
 
     formdata: {
       clear: Task.async(function* (range) {
         let seenException;
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
         try {
-          // Clear undo history of all searchBars
+          // Clear undo history of all search bars.
           let windows = Services.wm.getEnumerator("navigator:browser");
           while (windows.hasMoreElements()) {
             let currentWindow = windows.getNext();
             let currentDocument = currentWindow.document;
+
+            // searchBar.textbox may not exist due to the search bar binding
+            // not having been constructed yet if the search bar is in the
+            // overflow or menu panel. It won't have a value or edit history in
+            // that case.
             let searchBar = currentDocument.getElementById("searchbar");
-            if (searchBar)
+            if (searchBar && searchBar.textbox)
               searchBar.textbox.reset();
+
             let tabBrowser = currentWindow.gBrowser;
             if (!tabBrowser) {
               // No tab browser? This means that it's too early during startup (typically,
               // Session Restore hasn't completed yet). Since we don't have find
               // bars at that stage and since Session Restore will not restore
               // find bars further down during startup, we have nothing to clear.
               continue;
             }
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -360,16 +360,18 @@ skip-if = (os == 'linux') || (e10s && de
 [browser_purgehistory_clears_sh.js]
 [browser_PageMetaData_pushstate.js]
 [browser_refreshBlocker.js]
 support-files =
   refresh_header.sjs
   refresh_meta.sjs
 [browser_relatedTabs.js]
 [browser_remoteTroubleshoot.js]
+skip-if = !updater
+reason = depends on UpdateUtils .Locale
 support-files =
   test_remoteTroubleshoot.html
 [browser_remoteWebNavigation_postdata.js]
 [browser_removeTabsToTheEnd.js]
 [browser_restore_isAppTab.js]
 [browser_sanitize-passwordDisabledHosts.js]
 [browser_sanitize-sitepermissions.js]
 [browser_sanitize-timespans.js]
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 support-files =
   head.js
   privacypane_tests_perwindow.js
   site_data_test.html
 
 [browser_advanced_siteData.js]
 [browser_advanced_update.js]
+skip-if = !updater
 [browser_basic_rebuild_fonts_test.js]
 [browser_bug410900.js]
 [browser_bug705422.js]
 [browser_bug731866.js]
 [browser_bug795764_cachedisabled.js]
 [browser_bug1018066_resetScrollPosition.js]
 [browser_bug1020245_openPreferences_to_paneContent.js]
 [browser_bug1184989_prevent_scrolling_when_preferences_flipped.js]
--- a/browser/components/tests/browser/browser.ini
+++ b/browser/components/tests/browser/browser.ini
@@ -1,4 +1,6 @@
 [DEFAULT]
 
 [browser_bug538331.js]
+skip-if = !updater
+reason = test depends on update channel
 [browser_contentpermissionprompt.js]
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -5312,26 +5312,20 @@ def getJSToNativeConversionInfo(type, de
             #    it in the content compartment, we will try to call .then() on
             #    the chrome promise while in the content compartment, which will
             #    throw and we'll just get a rejected Promise.  Note that this is
             #    also the reason why a caller who has a chrome Promise
             #    representing an async operation can't itself convert it to a
             #    content-side Promise (at least not without some serious
             #    gyrations).
             # 3) Promise return value from a callback or callback interface.
-            #    This is in theory a case the spec covers but in practice it
-            #    really doesn't define behavior here because it doesn't define
-            #    what Realm we're in after the callback returns, which is when
-            #    the argument conversion happens.  We will use the current
-            #    compartment, which is the compartment of the callable (which
-            #    may itself be a cross-compartment wrapper itself), which makes
-            #    as much sense as anything else. In practice, such an API would
-            #    once again be providing a Promise to signal completion of an
-            #    operation, which would then not be exposed to anyone other than
-            #    our own implementation code.
+            #    Per spec, this should use the Realm of the callback object.  In
+            #    our case, that's the compartment of the underlying callback,
+            #    not the current compartment (which may be the compartment of
+            #    some cross-compartment wrapper around said callback).
             # 4) Return value from a JS-implemented interface.  In this case we
             #    have a problem.  Our current compartment is the compartment of
             #    the JS implementation.  But if the JS implementation returned
             #    a page-side Promise (which is a totally sane thing to do, and
             #    in fact the right thing to do given that this return value is
             #    going right to content script) then we don't want to
             #    Promise.resolve with our current compartment Promise, because
             #    that will wrap it up in a chrome-side Promise, which is
@@ -5363,16 +5357,24 @@ def getJSToNativeConversionInfo(type, de
                     if (!unwrappedVal) {
                       // A slight lie, but not much of one, for a dead object wrapper.
                       aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}"));
                       return nullptr;
                     }
                     globalObj = js::GetGlobalForObjectCrossCompartment(unwrappedVal);
                     """,
                     sourceDescription=sourceDescription)
+            elif isCallbackReturnValue == "Callback":
+                getPromiseGlobal = dedent(
+                    """
+                    // We basically want our entry global here.  Play it safe
+                    // and use GetEntryGlobal() to get it, with whatever
+                    // principal-clamping it ends up doing.
+                    globalObj = GetEntryGlobal()->GetGlobalJSObject();
+                    """)
             else:
                 getPromiseGlobal = ""
 
             templateBody = fill(
                 """
                 { // Scope for our GlobalObject, FastErrorResult, JSAutoCompartment,
                   // etc.
 
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -6393,27 +6393,27 @@ class Parser(Tokenizer):
     def p_NonAnyTypeSequenceType(self, p):
         """
             NonAnyType : SEQUENCE LT Type GT Null
         """
         innerType = p[3]
         type = IDLSequenceType(self.getLocation(p, 1), innerType)
         p[0] = self.handleNullable(type, p[5])
 
-    # Note: Promise<void> is allowed, so we want to parametrize on
-    # ReturnType, not Type.  Also, we want this to end up picking up
-    # the Promise interface for now, hence the games with IDLUnresolvedType.
+    # Note: Promise<void> is allowed, so we want to parametrize on ReturnType,
+    # not Type.  Also, we want this to end up picking up the Promise interface
+    # for now, hence the games with IDLUnresolvedType.  Promise types can't be
+    # null, hence no "Null" in there.
     def p_NonAnyTypePromiseType(self, p):
         """
-            NonAnyType : PROMISE LT ReturnType GT Null
+            NonAnyType : PROMISE LT ReturnType GT
         """
         innerType = p[3]
         promiseIdent = IDLUnresolvedIdentifier(self.getLocation(p, 1), "Promise")
-        type = IDLUnresolvedType(self.getLocation(p, 1), promiseIdent, p[3])
-        p[0] = self.handleNullable(type, p[5])
+        p[0] = IDLUnresolvedType(self.getLocation(p, 1), promiseIdent, p[3])
 
     def p_NonAnyTypeMozMapType(self, p):
         """
             NonAnyType : MOZMAP LT Type GT Null
         """
         innerType = p[3]
         type = IDLMozMapType(self.getLocation(p, 1), innerType)
         p[0] = self.handleNullable(type, p[5])
--- a/dom/bindings/parser/tests/test_promise.py
+++ b/dom/bindings/parser/tests/test_promise.py
@@ -44,16 +44,46 @@ def WebIDLTest(parser, harness):
         results = parser.finish();
     except:
         threw = True
     harness.ok(threw,
                "Should not allow overloads which have both Promise and "
                "non-Promise return types.")
 
     parser = parser.reset()
+    threw = False
+    try:
+        parser.parse("""
+            interface _Promise {};
+            interface A {
+              Promise<any>? foo();
+            };
+        """)
+        results = parser.finish();
+    except:
+        threw = True
+    harness.ok(threw,
+               "Should not allow nullable Promise return values.")
+
+    parser = parser.reset()
+    threw = False
+    try:
+        parser.parse("""
+            interface _Promise {};
+            interface A {
+              void foo(Promise<any>? arg);
+            };
+        """)
+        results = parser.finish();
+    except:
+        threw = True
+    harness.ok(threw,
+               "Should not allow nullable Promise arguments.")
+
+    parser = parser.reset()
     parser.parse("""
         interface _Promise {};
         interface A {
           Promise<any> foo();
           Promise<any> foo(long arg);
         };
     """)
     results = parser.finish();
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -748,23 +748,19 @@ public:
   void PassDateSequence(const Sequence<Date>&);
   void PassDateMozMap(const MozMap<Date>&);
   void PassNullableDateSequence(const Sequence<Nullable<Date> >&);
   Date ReceiveDate();
   Nullable<Date> ReceiveNullableDate();
 
   // Promise types
   void PassPromise(Promise&);
-  void PassNullablePromise(Promise*);
   void PassOptionalPromise(const Optional<OwningNonNull<Promise>>&);
-  void PassOptionalNullablePromise(const Optional<RefPtr<Promise>>&);
-  void PassOptionalNullablePromiseWithDefaultValue(Promise*);
   void PassPromiseSequence(const Sequence<OwningNonNull<Promise>>&);
   void PassPromiseMozMap(const MozMap<RefPtr<Promise>>&);
-  void PassNullablePromiseSequence(const Sequence<RefPtr<Promise>> &);
   Promise* ReceivePromise();
   already_AddRefed<Promise> ReceiveAddrefedPromise();
 
   // binaryNames tests
   void MethodRenamedTo();
   void OtherMethodRenamedTo();
   void MethodRenamedTo(int8_t);
   int8_t AttributeGetterRenamedTo();
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -726,22 +726,18 @@ interface TestInterface {
   void passDateSequence(sequence<Date> arg);
   void passNullableDateSequence(sequence<Date?> arg);
   void passDateMozMap(MozMap<Date> arg);
   Date receiveDate();
   Date? receiveNullableDate();
 
   // Promise types
   void passPromise(Promise<any> arg);
-  void passNullablePromise(Promise<any>? arg);
   void passOptionalPromise(optional Promise<any> arg);
-  void passOptionalNullablePromise(optional Promise<any>? arg);
-  void passOptionalNullablePromiseWithDefaultValue(optional Promise<any>? arg = null);
   void passPromiseSequence(sequence<Promise<any>> arg);
-  void passNullablePromiseSequence(sequence<Promise<any>?> arg);
   Promise<any> receivePromise();
   Promise<any> receiveAddrefedPromise();
 
   // binaryNames tests
   void methodRenamedFrom();
   [BinaryName="otherMethodRenamedTo"]
   void otherMethodRenamedFrom();
   void methodRenamedFrom(byte argument);
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -561,22 +561,18 @@ interface TestExampleInterface {
   void passDateSequence(sequence<Date> arg);
   void passNullableDateSequence(sequence<Date?> arg);
   void passDateMozMap(MozMap<Date> arg);
   Date receiveDate();
   Date? receiveNullableDate();
 
   // Promise types
   void passPromise(Promise<any> arg);
-  void passNullablePromise(Promise<any>? arg);
   void passOptionalPromise(optional Promise<any> arg);
-  void passOptionalNullablePromise(optional Promise<any>? arg);
-  void passOptionalNullablePromiseWithDefaultValue(optional Promise<any>? arg = null);
   void passPromiseSequence(sequence<Promise<any>> arg);
-  void passNullablePromiseSequence(sequence<Promise<any>?> arg);
   Promise<any> receivePromise();
   Promise<any> receiveAddrefedPromise();
 
   // binaryNames tests
   void methodRenamedFrom();
   [BinaryName="otherMethodRenamedTo"]
   void otherMethodRenamedFrom();
   void methodRenamedFrom(byte argument);
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -573,22 +573,18 @@ interface TestJSImplInterface {
   void passDateSequence(sequence<Date> arg);
   void passNullableDateSequence(sequence<Date?> arg);
   void passDateMozMap(MozMap<Date> arg);
   Date receiveDate();
   Date? receiveNullableDate();
 
   // Promise types
   void passPromise(Promise<any> arg);
-  void passNullablePromise(Promise<any>? arg);
   void passOptionalPromise(optional Promise<any> arg);
-  void passOptionalNullablePromise(optional Promise<any>? arg);
-  void passOptionalNullablePromiseWithDefaultValue(optional Promise<any>? arg = null);
   void passPromiseSequence(sequence<Promise<any>> arg);
-  void passNullablePromiseSequence(sequence<Promise<any>?> arg);
   Promise<any> receivePromise();
   Promise<any> receiveAddrefedPromise();
 
   // binaryNames tests
   void methodRenamedFrom();
   [BinaryName="otherMethodRenamedTo"]
   void otherMethodRenamedFrom();
   void methodRenamedFrom(byte argument);
--- a/dom/ipc/tests/process_error.xul
+++ b/dom/ipc/tests/process_error.xul
@@ -33,24 +33,28 @@
          'Subject implements nsIPropertyBag2.');
 
       var dumpID;
       if ('nsICrashReporter' in Components.interfaces) {
         dumpID = subject.getPropertyAsAString('dumpID');
         ok(dumpID, "dumpID is present and not an empty string");
       }
 
+      let p = Promise.resolve();
+
       if (dumpID) {
         var minidumpDirectory = getMinidumpDirectory();
-        removeFile(minidumpDirectory, dumpID + '.dmp');
-        removeFile(minidumpDirectory, dumpID + '.extra');
+        p = Services.crashmanager.ensureCrashIsPresent(dumpID).then(() => {
+          removeFile(minidumpDirectory, dumpID + '.dmp');
+          removeFile(minidumpDirectory, dumpID + '.extra');
+        });
       }
 
       Services.obs.removeObserver(crashObserver, 'ipc:content-shutdown');
-      done();
+      p.then(done);
     }
     Services.obs.addObserver(crashObserver, 'ipc:content-shutdown', false);
 
     document.getElementById('thebrowser')
             .QueryInterface(Components.interfaces.nsIFrameLoaderOwner)
             .frameLoader.messageManager
             .loadFrameScript('chrome://mochitests/content/chrome/dom/ipc/tests/process_error_contentscript.js', true);
   ]]></script>
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -560,17 +560,17 @@ struct AutoPaintSetup {
     // clear the clip rect. The other operators would be harder
     // but could be worth it to skip pushing a group.
     if (needsGroup) {
       mPaint.setBlendMode(SkBlendMode::kSrcOver);
       SkPaint temp;
       temp.setBlendMode(GfxOpToSkiaOp(aOptions.mCompositionOp));
       temp.setAlpha(ColorFloatToByte(aOptions.mAlpha));
       //TODO: Get a rect here
-      mCanvas->saveLayer(nullptr, &temp);
+      mCanvas->saveLayerPreserveLCDTextRequests(nullptr, &temp);
       mNeedsRestore = true;
     } else {
       mPaint.setAlpha(ColorFloatToByte(aOptions.mAlpha));
       mAlpha = aOptions.mAlpha;
     }
     mPaint.setFilterQuality(kLow_SkFilterQuality);
   }
 
@@ -837,18 +837,18 @@ DrawTargetSkia::Fill(const Path *aPath,
   }
 
   mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint);
 }
 
 bool
 DrawTargetSkia::ShouldLCDRenderText(FontType aFontType, AntialiasMode aAntialiasMode)
 {
-  // For non-opaque surfaces, only allow subpixel AA if explicitly permitted.
-  if (!IsOpaque(mFormat) && !mPermitSubpixelAA) {
+  // Only allow subpixel AA if explicitly permitted.
+  if (!GetPermitSubpixelAA()) {
     return false;
   }
 
   if (aAntialiasMode == AntialiasMode::DEFAULT) {
     switch (aFontType) {
       case FontType::MAC:
       case FontType::GDI:
       case FontType::DWRITE:
@@ -1036,23 +1036,25 @@ GfxMatrixToCGAffineTransform(const Matri
  * 3) The letter P right side up at (0, -20) due to the first flip
  * 4) The letter P right side up at (0, 80) due to the translation
  *
  * tl;dr - CGRects assume origin is bottom left, DrawTarget rects assume top left.
  */
 static bool
 SetupCGContext(DrawTargetSkia* aDT,
                CGContextRef aCGContext,
-               sk_sp<SkCanvas> aCanvas)
+               sk_sp<SkCanvas> aCanvas,
+               const IntPoint& aOrigin,
+               const IntSize& aSize)
 {
   // DrawTarget expects the origin to be at the top left, but CG
   // expects it to be at the bottom left. Transform to set the origin to
   // the top left. Have to set this before we do anything else.
   // This is transform (1) up top
-  CGContextTranslateCTM(aCGContext, 0, aDT->GetSize().height);
+  CGContextTranslateCTM(aCGContext, -aOrigin.x, aOrigin.y + aSize.height);
 
   // Transform (2) from the comments.
   CGContextScaleCTM(aCGContext, 1, -1);
 
   // Want to apply clips BEFORE the transform since the transform
   // will apply to the clips we apply.
   // CGClipApply applies clips in device space, so it would be a mistake
   // to transform these clips.
@@ -1097,29 +1099,30 @@ SetupCGGlyphs(CGContextRef aCGContext,
 // and transforms to the CGContext. See the comment above
 // SetupCGContext.
 CGContextRef
 DrawTargetSkia::BorrowCGContext(const DrawOptions &aOptions)
 {
   int32_t stride;
   SurfaceFormat format;
   IntSize size;
+  IntPoint origin;
 
   uint8_t* aSurfaceData = nullptr;
-  if (!LockBits(&aSurfaceData, &size, &stride, &format)) {
+  if (!LockBits(&aSurfaceData, &size, &stride, &format, &origin)) {
     NS_WARNING("Could not lock skia bits to wrap CG around");
     return nullptr;
   }
 
   if ((aSurfaceData == mCanvasData) && mCG && (mCGSize == size)) {
     // If our canvas data still points to the same data,
     // we can reuse the CG Context
     CGContextSaveGState(mCG);
     CGContextSetAlpha(mCG, aOptions.mAlpha);
-    SetupCGContext(this, mCG, mCanvas);
+    SetupCGContext(this, mCG, mCanvas, origin, size);
     return mCG;
   }
 
   if (!mColorSpace) {
     mColorSpace = (format == SurfaceFormat::A8) ?
                   CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB();
   }
 
@@ -1150,17 +1153,17 @@ DrawTargetSkia::BorrowCGContext(const Dr
     return nullptr;
   }
 
   CGContextSetAlpha(mCG, aOptions.mAlpha);
   CGContextSetShouldAntialias(mCG, aOptions.mAntialiasMode != AntialiasMode::NONE);
   CGContextSetShouldSmoothFonts(mCG, true);
   CGContextSetTextDrawingMode(mCG, kCGTextFill);
   CGContextSaveGState(mCG);
-  SetupCGContext(this, mCG, mCanvas);
+  SetupCGContext(this, mCG, mCanvas, origin, size);
   return mCG;
 }
 
 void
 DrawTargetSkia::ReturnCGContext(CGContextRef aCGContext)
 {
   MOZ_ASSERT(aCGContext == mCG);
   ReleaseBits(mCanvasData);
@@ -1768,16 +1771,17 @@ DrawTargetSkia::Init(const IntSize &aSiz
   mSurface = SkSurface::MakeRaster(info, stride, nullptr);
   if (!mSurface) {
     return false;
   }
 
   mSize = aSize;
   mFormat = aFormat;
   mCanvas = sk_ref_sp(mSurface->getCanvas());
+  SetPermitSubpixelAA(IsOpaque(mFormat));
 
   if (info.isOpaque()) {
     mCanvas->clear(SK_ColorBLACK);
   }
   return true;
 }
 
 bool
@@ -1796,16 +1800,17 @@ DrawTargetSkia::Init(SkCanvas* aCanvas)
     mCanvas->clear(clearColor);
   }
 
   SkISize size = mCanvas->getBaseLayerSize();
   mSize.width = size.width();
   mSize.height = size.height();
   mFormat = SkiaColorTypeToGfxFormat(imageInfo.colorType(),
                                      imageInfo.alphaType());
+  SetPermitSubpixelAA(IsOpaque(mFormat));
   return true;
 }
 
 #ifdef USE_SKIA_GPU
 /** Indicating a DT should be cached means that space will be reserved in Skia's cache
  * for the render target at creation time, with any unused resources exceeding the cache
  * limits being purged. When the DT is freed, it will then be guaranteed to be kept around
  * for subsequent allocations until it gets incidentally purged.
@@ -1845,16 +1850,17 @@ DrawTargetSkia::InitWithGrContext(GrCont
   if (!mSurface) {
     return false;
   }
 
   mGrContext = sk_ref_sp(aGrContext);
   mSize = aSize;
   mFormat = aFormat;
   mCanvas = sk_ref_sp(mSurface->getCanvas());
+  SetPermitSubpixelAA(IsOpaque(mFormat));
   return true;
 }
 
 #endif
 
 bool
 DrawTargetSkia::Init(unsigned char* aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat, bool aUninitialized)
 {
@@ -1864,16 +1870,17 @@ DrawTargetSkia::Init(unsigned char* aDat
   mSurface = SkSurface::MakeRasterDirect(MakeSkiaImageInfo(aSize, aFormat), aData, aStride);
   if (!mSurface) {
     return false;
   }
 
   mSize = aSize;
   mFormat = aFormat;
   mCanvas = sk_ref_sp(mSurface->getCanvas());
+  SetPermitSubpixelAA(IsOpaque(mFormat));
   return true;
 }
 
 void
 DrawTargetSkia::SetTransform(const Matrix& aTransform)
 {
   SkMatrix mat;
   GfxMatrixToSkiaMatrix(aTransform, mat);
@@ -1990,33 +1997,44 @@ CopyLayerImageFilter::toString(SkString*
 }
 #endif
 
 void
 DrawTargetSkia::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
                           const Matrix& aMaskTransform, const IntRect& aBounds,
                           bool aCopyBackground)
 {
-  PushedLayer layer(GetPermitSubpixelAA(), aOpaque, aOpacity, aMask, aMaskTransform);
+  PushedLayer layer(GetPermitSubpixelAA(), aOpaque, aOpacity, aMask, aMaskTransform,
+                    mCanvas->getTopDevice());
   mPushedLayers.push_back(layer);
 
   SkPaint paint;
 
   // If we have a mask, set the opacity to 0 so that SkCanvas::restore skips
   // implicitly drawing the layer so that we can properly mask it in PopLayer.
   paint.setAlpha(aMask ? 0 : ColorFloatToByte(aOpacity));
 
+  // aBounds is supplied in device space, but SaveLayerRec wants local space.
   SkRect bounds = IntRectToSkRect(aBounds);
+  if (!bounds.isEmpty()) {
+    SkMatrix inverseCTM;
+    if (mCanvas->getTotalMatrix().invert(&inverseCTM)) {
+      inverseCTM.mapRect(&bounds);
+    } else {
+      bounds.setEmpty();
+    }
+  }
 
   sk_sp<SkImageFilter> backdrop(aCopyBackground ? new CopyLayerImageFilter : nullptr);
 
   SkCanvas::SaveLayerRec saveRec(aBounds.IsEmpty() ? nullptr : &bounds,
                                  &paint,
                                  backdrop.get(),
-                                 aOpaque ? SkCanvas::kIsOpaque_SaveLayerFlag : 0);
+                                 SkCanvas::kPreserveLCDText_SaveLayerFlag |
+                                   (aOpaque ? SkCanvas::kIsOpaque_SaveLayerFlag : 0));
 
   mCanvas->saveLayer(saveRec);
 
   SetPermitSubpixelAA(aOpaque);
 
 #ifdef MOZ_WIDGET_COCOA
   CGContextRelease(mCG);
   mCG = nullptr;
@@ -2026,17 +2044,20 @@ DrawTargetSkia::PushLayer(bool aOpaque, 
 void
 DrawTargetSkia::PopLayer()
 {
   MarkChanged();
 
   MOZ_ASSERT(mPushedLayers.size());
   const PushedLayer& layer = mPushedLayers.back();
 
-  if (layer.mMask) {
+  // Ensure that the top device has actually changed. If it hasn't, then there
+  // is no layer image to be masked.
+  if (layer.mMask &&
+      layer.mPreviousDevice != mCanvas->getTopDevice()) {
     // If we have a mask, take a reference to the top layer's device so that
     // we can mask it ourselves. This assumes we forced SkCanvas::restore to
     // skip implicitly drawing the layer.
     sk_sp<SkBaseDevice> layerDevice = sk_ref_sp(mCanvas->getTopDevice());
     SkIRect layerBounds = layerDevice->getGlobalBounds();
     sk_sp<SkImage> layerImage;
     SkPixmap layerPixmap;
     if (layerDevice->peekPixels(&layerPixmap)) {
--- a/gfx/2d/DrawTargetSkia.h
+++ b/gfx/2d/DrawTargetSkia.h
@@ -171,28 +171,31 @@ private:
   bool UsingSkiaGPU() const;
 
   struct PushedLayer
   {
     PushedLayer(bool aOldPermitSubpixelAA,
                 bool aOpaque,
                 Float aOpacity,
                 SourceSurface* aMask,
-                const Matrix& aMaskTransform)
+                const Matrix& aMaskTransform,
+                SkBaseDevice* aPreviousDevice)
       : mOldPermitSubpixelAA(aOldPermitSubpixelAA),
         mOpaque(aOpaque),
         mOpacity(aOpacity),
         mMask(aMask),
-        mMaskTransform(aMaskTransform)
+        mMaskTransform(aMaskTransform),
+        mPreviousDevice(aPreviousDevice)
     {}
     bool mOldPermitSubpixelAA;
     bool mOpaque;
     Float mOpacity;
     RefPtr<SourceSurface> mMask;
     Matrix mMaskTransform;
+    SkBaseDevice* mPreviousDevice;
   };
   std::vector<PushedLayer> mPushedLayers;
 
 #ifdef USE_SKIA_GPU
   sk_sp<GrContext> mGrContext;
 #endif
 
   IntSize mSize;
--- a/gfx/2d/DrawTargetTiled.cpp
+++ b/gfx/2d/DrawTargetTiled.cpp
@@ -25,31 +25,33 @@ DrawTargetTiled::Init(const TileSet& aTi
 
   mTiles.reserve(aTiles.mTileCount);
   for (size_t i = 0; i < aTiles.mTileCount; ++i) {
     mTiles.push_back(TileInternal(aTiles.mTiles[i]));
     if (!aTiles.mTiles[i].mDrawTarget) {
       return false;
     }
     if (mTiles[0].mDrawTarget->GetFormat() != mTiles.back().mDrawTarget->GetFormat() ||
-        mTiles[0].mDrawTarget->GetBackendType() != mTiles.back().mDrawTarget->GetBackendType()) {
+        mTiles[0].mDrawTarget->GetBackendType() != mTiles.back().mDrawTarget->GetBackendType() ||
+        mTiles[0].mDrawTarget->GetPermitSubpixelAA() != mTiles.back().mDrawTarget->GetPermitSubpixelAA()) {
       return false;
     }
     uint32_t newXMost = max(mRect.XMost(),
                             mTiles[i].mTileOrigin.x + mTiles[i].mDrawTarget->GetSize().width);
     uint32_t newYMost = max(mRect.YMost(),
                             mTiles[i].mTileOrigin.y + mTiles[i].mDrawTarget->GetSize().height);
     mRect.x = min(mRect.x, mTiles[i].mTileOrigin.x);
     mRect.y = min(mRect.y, mTiles[i].mTileOrigin.y);
     mRect.width = newXMost - mRect.x;
     mRect.height = newYMost - mRect.y;
     mTiles[i].mDrawTarget->SetTransform(Matrix::Translation(mTiles[i].mTileOrigin.x,
                                                             mTiles[i].mTileOrigin.y));
   }
   mFormat = mTiles[0].mDrawTarget->GetFormat();
+  mPermitSubpixelAA = mTiles[0].mDrawTarget->GetPermitSubpixelAA();
   return true;
 }
 
 already_AddRefed<SourceSurface>
 DrawTargetTiled::Snapshot()
 {
   return MakeAndAddRef<SnapshotTiled>(mTiles, mRect);
 }
@@ -198,16 +200,25 @@ DrawTargetTiled::SetTransform(const Matr
     Matrix mat = aTransform;
     mat.PostTranslate(Float(-mTiles[i].mTileOrigin.x), Float(-mTiles[i].mTileOrigin.y));
     mTiles[i].mDrawTarget->SetTransform(mat);
   }
   DrawTarget::SetTransform(aTransform);
 }
 
 void
+DrawTargetTiled::SetPermitSubpixelAA(bool aPermitSubpixelAA)
+{
+  DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
+  for (size_t i = 0; i < mTiles.size(); i++) {
+    mTiles[i].mDrawTarget->SetPermitSubpixelAA(aPermitSubpixelAA);
+  }
+}
+
+void
 DrawTargetTiled::DrawSurface(SourceSurface* aSurface, const Rect& aDest, const Rect& aSource, const DrawSurfaceOptions& aSurfaceOptions, const DrawOptions& aDrawOptions)
 {
   Rect deviceRect = mTransform.TransformBounds(aDest);
   for (size_t i = 0; i < mTiles.size(); i++) {
     if (!mTiles[i].mClippedOut &&
         deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
                                    mTiles[i].mTileOrigin.y,
                                    mTiles[i].mDrawTarget->GetSize().width,
@@ -312,26 +323,38 @@ DrawTargetTiled::Fill(const Path* aPath,
 void
 DrawTargetTiled::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
                            const Matrix& aMaskTransform, const IntRect& aBounds,
                            bool aCopyBackground)
 {
   // XXX - not sure this is what we want or whether we want to continue drawing to a larger
   // intermediate surface, that would require tweaking the code in here a little though.
   for (size_t i = 0; i < mTiles.size(); i++) {
-    IntRect bounds = aBounds;
-    bounds.MoveBy(-mTiles[i].mTileOrigin);
-    mTiles[i].mDrawTarget->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, aBounds);
+    if (!mTiles[i].mClippedOut) {
+      IntRect bounds = aBounds;
+      bounds.MoveBy(-mTiles[i].mTileOrigin);
+      mTiles[i].mDrawTarget->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, bounds, aCopyBackground);
+    }
   }
+
+  PushedLayer layer(GetPermitSubpixelAA());
+  mPushedLayers.push_back(layer);
+  SetPermitSubpixelAA(aOpaque);
 }
 
 void
 DrawTargetTiled::PopLayer()
 {
   // XXX - not sure this is what we want or whether we want to continue drawing to a larger
   // intermediate surface, that would require tweaking the code in here a little though.
   for (size_t i = 0; i < mTiles.size(); i++) {
-    mTiles[i].mDrawTarget->PopLayer();
+    if (!mTiles[i].mClippedOut) {
+      mTiles[i].mDrawTarget->PopLayer();
+    }
   }
+
+  MOZ_ASSERT(mPushedLayers.size());
+  const PushedLayer& layer = mPushedLayers.back();
+  SetPermitSubpixelAA(layer.mOldPermitSubpixelAA);
 }
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/2d/DrawTargetTiled.h
+++ b/gfx/2d/DrawTargetTiled.h
@@ -109,16 +109,18 @@ public:
                          const Matrix& aMaskTransform,
                          const IntRect& aBounds = IntRect(),
                          bool aCopyBackground = false) override;
   virtual void PopLayer() override;
 
 
   virtual void SetTransform(const Matrix &aTransform) override;
 
+  virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) override;
+
   virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
                                                                   const IntSize &aSize,
                                                                   int32_t aStride,
                                                                   SurfaceFormat aFormat) const override
   {
     return mTiles[0].mDrawTarget->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
   }
   virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override
@@ -154,16 +156,25 @@ public:
   {
     return mTiles[0].mDrawTarget->CreateFilter(aType);
   }
 
 private:
   std::vector<TileInternal> mTiles;
   std::vector<std::vector<uint32_t> > mClippedOutTilesStack;
   IntRect mRect;
+
+  struct PushedLayer
+  {
+    explicit PushedLayer(bool aOldPermitSubpixelAA)
+      : mOldPermitSubpixelAA(aOldPermitSubpixelAA)
+    {}
+    bool mOldPermitSubpixelAA;
+  };
+  std::vector<PushedLayer> mPushedLayers;
 };
 
 class SnapshotTiled : public SourceSurface
 {
 public:
   SnapshotTiled(const std::vector<TileInternal>& aTiles, const IntRect& aRect)
     : mRect(aRect)
   {
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -767,36 +767,18 @@ gfxContext::Paint(gfxFloat alpha)
 
   mDT->FillRect(paintRect, PatternFromState(this),
                 DrawOptions(Float(alpha), GetOp()));
 }
 
 void
 gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform)
 {
-  if (gfxPrefs::UseNativePushLayer()) {
-    Save();
-    mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform);
-  } else {
-    DrawTarget* oldDT = mDT;
-
-    PushNewDT(content);
-
-    if (oldDT != mDT) {
-      PushClipsToDT(mDT);
-    }
-    mDT->SetTransform(GetDTTransform());
-
-    CurrentState().mBlendOpacity = aOpacity;
-    CurrentState().mBlendMask = aMask;
-#ifdef DEBUG
-    CurrentState().mWasPushedForBlendBack = true;
-#endif
-    CurrentState().mBlendMaskTransform = aMaskTransform;
-  }
+  Save();
+  mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform);
 }
 
 static gfxRect
 GetRoundOutDeviceClipExtents(gfxContext* aCtx)
 {
   gfxContextMatrixAutoSaveRestore save(aCtx);
   aCtx->SetMatrix(gfxMatrix());
   gfxRect r = aCtx->GetClipExtents();
@@ -811,140 +793,30 @@ gfxContext::PushGroupAndCopyBackground(g
   if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) {
     gfxRect clipRect = GetRoundOutDeviceClipExtents(this);
     clipExtents = IntRect::Truncate(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
   }
   bool pushOpaqueWithCopiedBG = (mDT->GetFormat() == SurfaceFormat::B8G8R8X8 ||
                                  mDT->GetOpaqueRect().Contains(clipExtents)) &&
                                 !mDT->GetUserData(&sDontUseAsSourceKey);
 
-  if (gfxPrefs::UseNativePushLayer()) {
-    Save();
-
-    if (pushOpaqueWithCopiedBG) {
-      mDT->PushLayer(true, aOpacity, aMask, aMaskTransform, IntRect(), true);
-    } else {
-      mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform, IntRect(), false);
-    }
-  } else {
-    RefPtr<SourceSurface> source;
-    // This snapshot can be nullptr if the DrawTarget is a cairo target that is currently
-    // in an error state.
-    if (pushOpaqueWithCopiedBG && (source = mDT->Snapshot())) {
-      DrawTarget *oldDT = mDT;
-      Point oldDeviceOffset = CurrentState().deviceOffset;
-
-      PushNewDT(gfxContentType::COLOR);
-
-      if (oldDT == mDT) {
-        // Creating new DT failed.
-        return;
-      }
-
-      CurrentState().mBlendOpacity = aOpacity;
-      CurrentState().mBlendMask = aMask;
-#ifdef DEBUG
-      CurrentState().mWasPushedForBlendBack = true;
-#endif
-      CurrentState().mBlendMaskTransform = aMaskTransform;
-
-      Point offset = CurrentState().deviceOffset - oldDeviceOffset;
-      Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
-      Rect sourceRect = surfRect + offset;
-
-      mDT->SetTransform(Matrix());
+  Save();
 
-      // XXX: It's really sad that we have to do this (for performance).
-      // Once DrawTarget gets a PushLayer API we can implement this within
-      // DrawTargetTiled.
-      if (source->GetType() == SurfaceType::TILED) {
-        SnapshotTiled *sourceTiled = static_cast<SnapshotTiled*>(source.get());
-        for (uint32_t i = 0; i < sourceTiled->mSnapshots.size(); i++) {
-          Rect tileSourceRect = sourceRect.Intersect(Rect(sourceTiled->mOrigins[i].x,
-                                                          sourceTiled->mOrigins[i].y,
-                                                          sourceTiled->mSnapshots[i]->GetSize().width,
-                                                          sourceTiled->mSnapshots[i]->GetSize().height));
-
-          if (tileSourceRect.IsEmpty()) {
-            continue;
-          }
-          Rect tileDestRect = tileSourceRect - offset;
-          tileSourceRect -= sourceTiled->mOrigins[i];
-
-          mDT->DrawSurface(sourceTiled->mSnapshots[i], tileDestRect, tileSourceRect);
-        }
-      } else {
-        mDT->DrawSurface(source, surfRect, sourceRect);
-      }
-      mDT->SetOpaqueRect(oldDT->GetOpaqueRect());
-
-      PushClipsToDT(mDT);
-      mDT->SetTransform(GetDTTransform());
-      return;
-    }
-    DrawTarget* oldDT = mDT;
-
-    PushNewDT(content);
-
-    if (oldDT != mDT) {
-      PushClipsToDT(mDT);
-    }
-
-    mDT->SetTransform(GetDTTransform());
-    CurrentState().mBlendOpacity = aOpacity;
-    CurrentState().mBlendMask = aMask;
-#ifdef DEBUG
-    CurrentState().mWasPushedForBlendBack = true;
-#endif
-    CurrentState().mBlendMaskTransform = aMaskTransform;
+  if (pushOpaqueWithCopiedBG) {
+    mDT->PushLayer(true, aOpacity, aMask, aMaskTransform, IntRect(), true);
+  } else {
+    mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform, IntRect(), false);
   }
 }
 
 void
 gfxContext::PopGroupAndBlend()
 {
-  if (gfxPrefs::UseNativePushLayer()) {
-    mDT->PopLayer();
-    Restore();
-  } else {
-    MOZ_ASSERT(CurrentState().mWasPushedForBlendBack);
-    Float opacity = CurrentState().mBlendOpacity;
-    RefPtr<SourceSurface> mask = CurrentState().mBlendMask;
-    Matrix maskTransform = CurrentState().mBlendMaskTransform;
-
-    RefPtr<SourceSurface> src = mDT->Snapshot();
-    Point deviceOffset = CurrentState().deviceOffset;
-    Restore();
-    CurrentState().sourceSurfCairo = nullptr;
-    CurrentState().sourceSurface = src;
-    CurrentState().sourceSurfaceDeviceOffset = deviceOffset;
-    CurrentState().pattern = nullptr;
-    CurrentState().patternTransformChanged = false;
-
-    Matrix mat = mTransform;
-    mat.Invert();
-    mat.PreTranslate(deviceOffset.x, deviceOffset.y); // device offset translation
-
-    CurrentState().surfTransform = mat;
-
-    CompositionOp oldOp = GetOp();
-    SetOp(CompositionOp::OP_OVER);
-
-    if (mask) {
-      if (!maskTransform.HasNonTranslation()) {
-        Mask(mask, opacity, Point(maskTransform._31, maskTransform._32));
-      } else {
-        Mask(mask, opacity, maskTransform);
-      }
-    } else {
-      Paint(opacity);
-    }
-
-    SetOp(oldOp);
-  }
+  mDT->PopLayer();
+  Restore();
 }
 
 #ifdef MOZ_DUMP_PAINTING
 void
 gfxContext::WriteAsPNG(const char* aFile)
 {
   gfxUtils::WriteAsPNG(mDT, aFile);
 }
@@ -1062,34 +934,16 @@ gfxContext::FillAzure(const Pattern& aPa
       mDT->FillRect(mRect, aPattern, DrawOptions(aOpacity, op, state.aaMode));
     }
   } else {
     EnsurePath();
     mDT->Fill(mPath, aPattern, DrawOptions(aOpacity, op, state.aaMode));
   }
 }
 
-void
-gfxContext::PushClipsToDT(DrawTarget *aDT)
-{
-  // Don't need to save the old transform, we'll be setting a new one soon!
-
-  // Push all clips from the bottom of the stack to the clip before ours.
-  for (unsigned int i = 0; i < mStateStack.Length() - 1; i++) {
-    for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
-      aDT->SetTransform(mStateStack[i].pushedClips[c].transform * GetDeviceTransform());
-      if (mStateStack[i].pushedClips[c].path) {
-        aDT->PushClip(mStateStack[i].pushedClips[c].path);
-      } else {
-        aDT->PushClipRect(mStateStack[i].pushedClips[c].rect);
-      }
-    }
-  }
-}
-
 CompositionOp
 gfxContext::GetOp()
 {
   if (CurrentState().op != CompositionOp::OP_SOURCE) {
     return CurrentState().op;
   }
 
   AzureState &state = CurrentState();
@@ -1206,50 +1060,8 @@ gfxContext::GetDeviceTransform() const
 Matrix
 gfxContext::GetDTTransform() const
 {
   Matrix mat = mTransform;
   mat._31 -= CurrentState().deviceOffset.x;
   mat._32 -= CurrentState().deviceOffset.y;
   return mat;
 }
-
-void
-gfxContext::PushNewDT(gfxContentType content)
-{
-  Rect clipBounds = GetAzureDeviceSpaceClipBounds();
-  clipBounds.RoundOut();
-
-  clipBounds.width = std::max(1.0f, clipBounds.width);
-  clipBounds.height = std::max(1.0f, clipBounds.height);
-
-  SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content);
-
-  RefPtr<DrawTarget> newDT =
-    mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)),
-                                 format);
-
-  if (!newDT) {
-    NS_WARNING("Failed to create DrawTarget of sufficient size.");
-    newDT = mDT->CreateSimilarDrawTarget(IntSize(64, 64), format);
-
-    if (!newDT) {
-      if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()
-#ifdef XP_WIN
-          && !(mDT->GetBackendType() == BackendType::DIRECT2D1_1 &&
-               !DeviceManagerDx::Get()->GetContentDevice())
-#endif
-          ) {
-        // If even this fails.. we're most likely just out of memory!
-        NS_ABORT_OOM(BytesPerPixel(format) * 64 * 64);
-      }
-      newDT = CurrentState().drawTarget;
-    }
-  }
-
-  Save();
-
-  CurrentState().drawTarget = newDT;
-  CurrentState().deviceOffset = clipBounds.TopLeft();
-
-  mDT = newDT;
-}
-
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -512,23 +512,21 @@ private:
 #endif
   };
 
   // This ensures mPath contains a valid path (in user space!)
   void EnsurePath();
   // This ensures mPathBuilder contains a valid PathBuilder (in user space!)
   void EnsurePathBuilder();
   void FillAzure(const Pattern& aPattern, mozilla::gfx::Float aOpacity);
-  void PushClipsToDT(mozilla::gfx::DrawTarget *aDT);
   CompositionOp GetOp();
   void ChangeTransform(const mozilla::gfx::Matrix &aNewMatrix, bool aUpdatePatternTransform = true);
   Rect GetAzureDeviceSpaceClipBounds();
   Matrix GetDeviceTransform() const;
   Matrix GetDTTransform() const;
-  void PushNewDT(gfxContentType content);
 
   bool mPathIsRect;
   bool mTransformChanged;
   Matrix mPathTransform;
   Rect mRect;
   RefPtr<PathBuilder> mPathBuilder;
   RefPtr<Path> mPath;
   Matrix mTransform;
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -397,17 +397,16 @@ private:
   DECL_GFX_PREF(Live, "gfx.partialpresent.force",              PartialPresent, int32_t, 0);
   DECL_GFX_PREF(Live, "gfx.perf-warnings.enabled",             PerfWarnings, bool, false);
   DECL_GFX_PREF(Live, "gfx.SurfaceTexture.detach.enabled",     SurfaceTextureDetachEnabled, bool, true);
   DECL_GFX_PREF(Live, "gfx.testing.device-reset",              DeviceResetForTesting, int32_t, 0);
   DECL_GFX_PREF(Live, "gfx.testing.device-fail",               DeviceFailForTesting, bool, false);
   DECL_GFX_PREF(Once, "gfx.text.disable-aa",                   DisableAllTextAA, bool, false);
   DECL_GFX_PREF(Live, "gfx.ycbcr.accurate-conversion",         YCbCrAccurateConversion, bool, false);
 
-  DECL_GFX_PREF(Live, "gfx.content.use-native-pushlayer",      UseNativePushLayer, bool, false);
   DECL_GFX_PREF(Live, "gfx.content.always-paint",              AlwaysPaint, bool, false);
 
   // Disable surface sharing due to issues with compatible FBConfigs on
   // NVIDIA drivers as described in bug 1193015.
   DECL_GFX_PREF(Live, "gfx.use-glx-texture-from-pixmap",       UseGLXTextureFromPixmap, bool, false);
 
   DECL_GFX_PREF(Once, "gfx.use-iosurface-textures",            UseIOSurfaceTextures, bool, false);
 
--- a/ipc/glue/CrashReporterHost.cpp
+++ b/ipc/glue/CrashReporterHost.cpp
@@ -1,20 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "CrashReporterHost.h"
 #include "CrashReporterMetadataShmem.h"
+#include "mozilla/LazyIdleThread.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/Telemetry.h"
 #ifdef MOZ_CRASHREPORTER
+# include "nsIAsyncShutdown.h"
 # include "nsICrashService.h"
 #endif
 
 namespace mozilla {
 namespace ipc {
 
 CrashReporterHost::CrashReporterHost(GeckoProcessType aProcessType,
                                      const Shmem& aShmem)
@@ -58,38 +60,173 @@ CrashReporterHost::GenerateCrashReport(R
   notes.Put(NS_LITERAL_CSTRING("StartupTime"), nsDependentCString(startTime));
 
   CrashReporterMetadataShmem::ReadAppNotes(mShmem, &notes);
 
   CrashReporter::AppendExtraData(dumpID, notes);
   NotifyCrashService(mProcessType, dumpID, &notes);
 }
 
+/**
+ * Runnable used to execute the minidump analyzer program asynchronously after
+ * a crash. This should run on a background thread not to block the main thread
+ * over the potentially long minidump analysis. Once analysis is over, the
+ * crash information will be relayed to the crash manager via another runnable
+ * sent to the main thread. Shutdown will be blocked for the duration of the
+ * entire process to ensure this information is sent.
+ */
+class AsyncMinidumpAnalyzer final : public nsIRunnable
+                                  , public nsIAsyncShutdownBlocker
+{
+public:
+  /**
+   * Create a new minidump analyzer runnable, this will also block shutdown
+   * until the associated crash has been added to the crash manager.
+   */
+  AsyncMinidumpAnalyzer(int32_t aProcessType,
+                        int32_t aCrashType,
+                        const nsString& aChildDumpID)
+    : mProcessType(aProcessType)
+    , mCrashType(aCrashType)
+    , mChildDumpID(aChildDumpID)
+    , mName(NS_LITERAL_STRING("Crash reporter: blocking on minidump analysis"))
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsresult rv = GetShutdownBarrier()->AddBlocker(
+      this, NS_LITERAL_STRING(__FILE__), __LINE__,
+      NS_LITERAL_STRING("Minidump analysis"));
+    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+  }
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    if (mProcessType == nsICrashService::PROCESS_TYPE_CONTENT) {
+      CrashReporter::RunMinidumpAnalyzer(mChildDumpID);
+    }
+
+    // Make a copy of these so we can copy them into the runnable lambda
+    int32_t processType = mProcessType;
+    int32_t crashType = mCrashType;
+    nsString childDumpID(mChildDumpID);
+
+    NS_DispatchToMainThread(NS_NewRunnableFunction([=] () -> void {
+      nsCOMPtr<nsICrashService> crashService =
+        do_GetService("@mozilla.org/crashservice;1");
+      if (crashService) {
+        crashService->AddCrash(processType, crashType, childDumpID);
+      }
+
+      nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
+
+      if (barrier) {
+        barrier->RemoveBlocker(this);
+      }
+    }));
+
+    return NS_OK;
+  }
+
+  NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aBarrierClient) override
+  {
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetName(nsAString& aName) override
+  {
+    aName = mName;
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetState(nsIPropertyBag**) override
+  {
+    return NS_OK;
+  }
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+private:
+  ~AsyncMinidumpAnalyzer() {}
+
+  // Returns the profile-before-change shutdown barrier
+  static nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
+    nsCOMPtr<nsIAsyncShutdownClient> barrier;
+    nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier));
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
+
+    return barrier.forget();
+  }
+
+  int32_t mProcessType;
+  int32_t mCrashType;
+  const nsString mChildDumpID;
+  const nsString mName;
+};
+
+NS_IMPL_ISUPPORTS(AsyncMinidumpAnalyzer, nsIRunnable, nsIAsyncShutdownBlocker)
+
+/**
+ * Add information about a crash to the crash manager. This method runs the
+ * various activities required to gather additional information and notify the
+ * crash manager asynchronously, since many of them involve I/O or potentially
+ * long processing.
+ *
+ * @param aProcessType The type of process that crashed
+ * @param aCrashType The type of crash (crash or hang)
+ * @param aChildDumpID A string holding the ID of the dump associated with this
+ *        crash
+ */
+/* static */ void
+CrashReporterHost::AsyncAddCrash(int32_t aProcessType,
+                                 int32_t aCrashType,
+                                 const nsString& aChildDumpID)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  static StaticRefPtr<LazyIdleThread> sBackgroundThread;
+
+  if (!sBackgroundThread) {
+    // Background thread used for running minidump analysis. It will be
+    // destroyed immediately after it's done with the task.
+    sBackgroundThread =
+      new LazyIdleThread(0, NS_LITERAL_CSTRING("CrashReporterHost"));
+    ClearOnShutdown(&sBackgroundThread);
+  }
+
+  RefPtr<AsyncMinidumpAnalyzer> task =
+    new AsyncMinidumpAnalyzer(aProcessType, aCrashType, aChildDumpID);
+
+  Unused << sBackgroundThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
 /* static */ void
 CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType,
                                       const nsString& aChildDumpID,
                                       const AnnotationTable* aNotes)
 {
   if (!NS_IsMainThread()) {
     RefPtr<Runnable> runnable = NS_NewRunnableFunction([=] () -> void {
       CrashReporterHost::NotifyCrashService(aProcessType, aChildDumpID, aNotes);
     });
     RefPtr<nsIThread> mainThread = do_GetMainThread();
     SyncRunnable::DispatchToThread(mainThread, runnable);
     return;
   }
 
   MOZ_ASSERT(!aChildDumpID.IsEmpty());
 
-  nsCOMPtr<nsICrashService> crashService =
-    do_GetService("@mozilla.org/crashservice;1");
-  if (!crashService) {
-    return;
-  }
-
   int32_t processType;
   int32_t crashType = nsICrashService::CRASH_TYPE_CRASH;
 
   nsCString telemetryKey;
 
   switch (aProcessType) {
     case GeckoProcessType_Content:
       processType = nsICrashService::PROCESS_TYPE_CONTENT;
@@ -114,15 +251,15 @@ CrashReporterHost::NotifyCrashService(Ge
       processType = nsICrashService::PROCESS_TYPE_GPU;
       telemetryKey.AssignLiteral("gpu");
       break;
     default:
       NS_ERROR("unknown process type");
       return;
   }
 
-  crashService->AddCrash(processType, crashType, aChildDumpID);
+  AsyncAddCrash(processType, crashType, aChildDumpID);
   Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, telemetryKey, 1);
 }
 #endif
 
 } // namespace ipc
 } // namespace mozilla
--- a/ipc/glue/CrashReporterHost.h
+++ b/ipc/glue/CrashReporterHost.h
@@ -45,16 +45,18 @@ public:
   static void NotifyCrashService(
     GeckoProcessType aProcessType,
     const nsString& aChildDumpID,
     const AnnotationTable* aNotes);
 #endif
 
 private:
   void GenerateCrashReport(RefPtr<nsIFile> aCrashDump);
+  static void AsyncAddCrash(int32_t aProcessType, int32_t aCrashType,
+                            const nsString& aChildDumpID);
 
 private:
   GeckoProcessType mProcessType;
   Shmem mShmem;
   time_t mStartTime;
 };
 
 } // namespace ipc
--- a/js/src/jit-test/tests/wasm/basic.js
+++ b/js/src/jit-test/tests/wasm/basic.js
@@ -13,19 +13,19 @@ var o = wasmEvalText('(module (func) (ex
 var names = Object.getOwnPropertyNames(o);
 assertEq(names.length, 1);
 assertEq(names[0], 'a');
 var desc = Object.getOwnPropertyDescriptor(o, 'a');
 assertEq(typeof desc.value, "function");
 assertEq(desc.value.name, "wasm-function[0]");
 assertEq(desc.value.length, 0);
 assertEq(desc.value(), undefined);
-assertEq(desc.writable, true);
+assertEq(desc.writable, false);
 assertEq(desc.enumerable, true);
-assertEq(desc.configurable, true);
+assertEq(desc.configurable, false);
 assertEq(desc.value(), undefined);
 
 wasmValidateText('(module (func) (func) (export "a" 0))');
 wasmValidateText('(module (func) (func) (export "a" 1))');
 wasmValidateText('(module (func $a) (func $b) (export "a" $a) (export "b" $b))');
 wasmValidateText('(module (func $a) (func $b) (export "a" $a) (export "b" $b))');
 
 wasmFailValidateText('(module (func) (export "a" 1))', /exported function index out of bounds/);
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -1,15 +1,16 @@
 load(libdir + "wasm.js");
 load(libdir + "wasm-binary.js");
 
+const Module = WebAssembly.Module;
 const CompileError = WebAssembly.CompileError;
 
 const magicError = /failed to match magic number/;
-const unknownSection = /expected user-defined section/;
+const unknownSection = /expected custom section/;
 
 function sectionError(section) {
     return RegExp(`failed to start ${section} section`);
 }
 
 function versionError(actual) {
     var expect = experimentalVersion;
     var str = `binary version 0x${actual.toString(16)} does not match expected version 0x${expect.toString(16)}`;
@@ -47,17 +48,17 @@ function varS32(s32) {
             byte |= 0x80;
         bytes.push(byte);
     } while (s32 != 0 && s32 != -1);
     return bytes;
 }
 
 const U32MAX_LEB = [255, 255, 255, 255, 15];
 
-const wasmEval = (code, imports) => new WebAssembly.Instance(new WebAssembly.Module(code), imports).exports;
+const wasmEval = (code, imports) => new WebAssembly.Instance(new Module(code), imports).exports;
 
 assertErrorMessage(() => wasmEval(toU8([])), CompileError, magicError);
 assertErrorMessage(() => wasmEval(toU8([42])), CompileError, magicError);
 assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2])), CompileError, magicError);
 assertErrorMessage(() => wasmEval(toU8([1,2,3,4])), CompileError, magicError);
 assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3])), CompileError, versionError(0x6d736100));
 assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, 1])), CompileError, versionError(0x6d736100));
 assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, ver0])), CompileError, versionError(0x6d736100));
@@ -84,22 +85,22 @@ assertErrorMessage(() => wasmEval(toU8(m
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(dataId))), CompileError, sectionError("data"));
 
 // unknown sections are unconditionally rejected
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42))), CompileError, unknownSection);
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42, 0))), CompileError, unknownSection);
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42, 1, 0))), CompileError, unknownSection);
 
 // user sections have special rules
-assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0))), CompileError, sectionError("user-defined"));  // no length
-assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0))), CompileError, sectionError("user-defined"));  // no id
-assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0, 0))), CompileError, sectionError("user-defined"));  // payload too small to have id length
-assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1))), CompileError, sectionError("user-defined"));  // id not present
-assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1, 65))), CompileError, sectionError("user-defined"));  // id length doesn't fit in section
-assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 0, 0))), CompileError, sectionError("user-defined"));  // second, unfinished user-defined section
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0))), CompileError, sectionError("custom"));  // no length
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0))), CompileError, sectionError("custom"));  // no id
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0, 0))), CompileError, sectionError("custom"));  // payload too small to have id length
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1))), CompileError, sectionError("custom"));  // id not present
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1, 65))), CompileError, sectionError("custom"));  // id length doesn't fit in section
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 0, 0))), CompileError, sectionError("custom"));  // second, unfinished custom section
 wasmEval(toU8(moduleHeaderThen(0, 1, 0)));  // empty id
 wasmEval(toU8(moduleHeaderThen(0, 1, 0,  0, 1, 0)));  // 2x empty id
 wasmEval(toU8(moduleHeaderThen(0, 2, 1, 65)));  // id = "A"
 
 function string(name) {
     var nameBytes = name.split('').map(c => {
         var code = c.charCodeAt(0);
         assertEq(code < 128, true); // TODO
@@ -229,17 +230,17 @@ function nameSection(elems) {
         }
         body.push(...varU32(fn.locals.length));
         for (let local of fn.locals)
             body.push(...encodedString(local.name, local.nameLen));
     }
     return { name: userDefinedId, body };
 }
 
-function userDefinedSection(name, ...body) {
+function customSection(name, ...body) {
     return { name: userDefinedId, body: [...string(name), ...body] };
 }
 
 const v2vSig = {args:[], ret:VoidCode};
 const i2vSig = {args:[I32Code], ret:VoidCode};
 const v2vBody = funcBody({locals:[], body:[]});
 
 assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: U32MAX_LEB } ])), CompileError, /too many signatures/);
@@ -322,46 +323,76 @@ wasmEval(moduleWithSections([sigSection(
 
 // Ignore errors in name section.
 var tooBigNameSection = {
     name: userDefinedId,
     body: [...string(nameName), ...varU32(Math.pow(2, 31))] // declare 2**31 functions.
 };
 wasmEval(moduleWithSections([tooBigNameSection]));
 
-// Skip user-defined sections before any expected section
-var userDefSec = userDefinedSection("wee", 42, 13);
+// Skip custom sections before any expected section
+var customDefSec = customSection("wee", 42, 13);
 var sigSec = sigSection([v2vSig]);
 var declSec = declSection([0]);
 var bodySec = bodySection([v2vBody]);
-wasmEval(moduleWithSections([userDefSec, sigSec, declSec, bodySec]));
-wasmEval(moduleWithSections([sigSec, userDefSec, declSec, bodySec]));
-wasmEval(moduleWithSections([sigSec, declSec, userDefSec, bodySec]));
-wasmEval(moduleWithSections([sigSec, declSec, bodySec, userDefSec]));
-wasmEval(moduleWithSections([userDefSec, userDefSec, sigSec, declSec, bodySec]));
-wasmEval(moduleWithSections([userDefSec, userDefSec, sigSec, userDefSec, declSec, userDefSec, bodySec]));
+var nameSec = nameSection([{name:'hi'}]);
+wasmEval(moduleWithSections([customDefSec, sigSec, declSec, bodySec]));
+wasmEval(moduleWithSections([sigSec, customDefSec, declSec, bodySec]));
+wasmEval(moduleWithSections([sigSec, declSec, customDefSec, bodySec]));
+wasmEval(moduleWithSections([sigSec, declSec, bodySec, customDefSec]));
+wasmEval(moduleWithSections([customDefSec, customDefSec, sigSec, declSec, bodySec]));
+wasmEval(moduleWithSections([customDefSec, customDefSec, sigSec, customDefSec, declSec, customDefSec, bodySec]));
+
+// custom sections reflection:
+function checkCustomSection(buf, val) {
+    assertEq(buf instanceof ArrayBuffer, true);
+    assertEq(buf.byteLength, 1);
+    assertEq(new Uint8Array(buf)[0], val);
+}
+var custom1 = customSection("one", 1);
+var custom2 = customSection("one", 2);
+var custom3 = customSection("two", 3);
+var custom4 = customSection("three", 4);
+var custom5 = customSection("three", 5);
+var custom6 = customSection("three", 6);
+var m = new Module(moduleWithSections([custom1, sigSec, custom2, declSec, custom3, bodySec, custom4, nameSec, custom5, custom6]));
+var arr = Module.customSections(m, "one");
+assertEq(arr.length, 2);
+checkCustomSection(arr[0], 1);
+checkCustomSection(arr[1], 2);
+var arr = Module.customSections(m, "two");
+assertEq(arr.length, 1);
+checkCustomSection(arr[0], 3);
+var arr = Module.customSections(m, "three");
+assertEq(arr.length, 3);
+checkCustomSection(arr[0], 4);
+checkCustomSection(arr[1], 5);
+checkCustomSection(arr[2], 6);
+var arr = Module.customSections(m, "name");
+assertEq(arr.length, 1);
+assertEq(arr[0].byteLength, nameSec.body.length - 5 /* 4name */);
 
 // Diagnose nonstandard block signature types.
 for (var bad of [0xff, 0, 1, 0x3f])
     assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid inline block type/);
 
 // Checking stack trace.
 function runStackTraceTest(namesContent, expectedName) {
     var sections = [
         sigSection([v2vSig]),
         importSection([{sigIndex:0, module:"env", func:"callback"}]),
         declSection([0]),
         exportSection([{funcIndex:1, name: "run"}]),
         bodySection([funcBody({locals: [], body: [CallCode, varU32(0)]})]),
-        userDefinedSection("whoa"),
-        userDefinedSection("wee", 42),
+        customSection("whoa"),
+        customSection("wee", 42),
     ];
     if (namesContent)
         sections.push(nameSection(namesContent));
-    sections.push(userDefinedSection("yay", 13));
+    sections.push(customSection("yay", 13));
 
     var result = "";
     var callback = () => {
         var prevFrameEntry = new Error().stack.split('\n')[1];
         result = prevFrameEntry.split('@')[0];
     };
     wasmEval(moduleWithSections(sections), {"env": { callback }}).run();
     assertEq(result, expectedName);
--- a/js/src/jit-test/tests/wasm/jsapi.js
+++ b/js/src/jit-test/tests/wasm/jsapi.js
@@ -151,16 +151,33 @@ assertEq(arr[0].kind, "function");
 assertEq(arr[0].name, "a");
 assertEq(arr[1].kind, "memory");
 assertEq(arr[1].name, "b");
 assertEq(arr[2].kind, "table");
 assertEq(arr[2].name, "c");
 assertEq(arr[3].kind, "global");
 assertEq(arr[3].name, "⚡");
 
+// 'WebAssembly.Module.customSections' data property
+const customSectionsDesc = Object.getOwnPropertyDescriptor(Module, 'customSections');
+assertEq(typeof customSectionsDesc.value, "function");
+assertEq(customSectionsDesc.writable, true);
+assertEq(customSectionsDesc.enumerable, false);
+assertEq(customSectionsDesc.configurable, true);
+
+// 'WebAssembly.Module.customSections' method
+const moduleCustomSections = customSectionsDesc.value;
+assertEq(moduleCustomSections.length, 2);
+assertErrorMessage(() => moduleCustomSections(), TypeError, /requires more than 0 arguments/);
+assertErrorMessage(() => moduleCustomSections(undefined), TypeError, /first argument must be a WebAssembly.Module/);
+assertErrorMessage(() => moduleCustomSections({}), TypeError, /first argument must be a WebAssembly.Module/);
+var arr = moduleCustomSections(emptyModule);
+assertEq(arr instanceof Array, true);
+assertEq(arr.length, 0);
+
 // 'WebAssembly.Instance' data property
 const instanceDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Instance');
 assertEq(typeof instanceDesc.value, "function");
 assertEq(instanceDesc.writable, true);
 assertEq(instanceDesc.enumerable, false);
 assertEq(instanceDesc.configurable, true);
 
 // 'WebAssembly.Instance' constructor function
@@ -196,18 +213,31 @@ assertEq(Object.getPrototypeOf(exporting
 
 // 'WebAssembly.Instance' 'exports' data property
 const instanceExportsDesc = Object.getOwnPropertyDescriptor(exportingInstance, 'exports');
 assertEq(typeof instanceExportsDesc.value, "object");
 assertEq(instanceExportsDesc.writable, true);
 assertEq(instanceExportsDesc.enumerable, true);
 assertEq(instanceExportsDesc.configurable, true);
 
+// 'WebAssembly.Instance' 'exports' object
+const exportsObj = exportingInstance.exports;
+assertEq(typeof exportsObj, "object");
+assertEq(Object.isExtensible(exportsObj), false);
+assertEq(Object.getPrototypeOf(exportsObj), null);
+assertEq(Object.keys(exportsObj).join(), "f");
+exportsObj.g = 1;
+assertEq(Object.keys(exportsObj).join(), "f");
+assertErrorMessage(() => Object.setPrototypeOf(exportsObj, {}), TypeError, /can't set prototype of this object/);
+assertEq(Object.getPrototypeOf(exportsObj), null);
+assertErrorMessage(() => Object.defineProperty(exportsObj, 'g', {}), TypeError, /Object is not extensible/);
+assertEq(Object.keys(exportsObj).join(), "f");
+
 // Exported WebAssembly functions
-const f = exportingInstance.exports.f;
+const f = exportsObj.f;
 assertEq(f instanceof Function, true);
 assertEq(f.length, 0);
 assertEq('name' in f, true);
 assertEq(Function.prototype.call.call(f), 42);
 assertErrorMessage(() => new f(), TypeError, /is not a constructor/);
 
 // 'WebAssembly.Memory' data property
 const memoryDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Memory');
--- a/js/src/jit-test/tests/wasm/table-gc.js
+++ b/js/src/jit-test/tests/wasm/table-gc.js
@@ -21,89 +21,86 @@ var i = wasmEvalText(`(module (table 2 a
 var e = i.exports;
 var t = e.tbl;
 var f = t.get(0);
 assertEq(f(), e.call(0));
 assertErrorMessage(() => e.call(1), RuntimeError, /indirect call to null/);
 assertErrorMessage(() => e.call(2), RuntimeError, /index out of bounds/);
 assertEq(finalizeCount(), 0);
 i.edge = makeFinalizeObserver();
-e.edge = makeFinalizeObserver();
 t.edge = makeFinalizeObserver();
 f.edge = makeFinalizeObserver();
 gc();
 assertEq(finalizeCount(), 0);
 f = null;
 gc();
 assertEq(finalizeCount(), 1);
 f = t.get(0);
 f.edge = makeFinalizeObserver();
 gc();
 assertEq(finalizeCount(), 1);
 i.exports = null;
 e = null;
 gc();
-assertEq(finalizeCount(), 2);
+assertEq(finalizeCount(), 1);
 t = null;
 gc();
-assertEq(finalizeCount(), 2);
+assertEq(finalizeCount(), 1);
 i = null;
 gc();
-assertEq(finalizeCount(), 2);
+assertEq(finalizeCount(), 1);
 assertEq(f(), 0);
 f = null;
 gc();
-assertEq(finalizeCount(), 5);
+assertEq(finalizeCount(), 4);
 
 // A table should hold the instance of any of its elements alive.
 resetFinalizeCount();
 var i = wasmEvalText(`(module (table 1 anyfunc) (export "tbl" table) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`);
 var e = i.exports;
 var t = e.tbl;
 var f = t.get(0);
 i.edge = makeFinalizeObserver();
-e.edge = makeFinalizeObserver();
 t.edge = makeFinalizeObserver();
 f.edge = makeFinalizeObserver();
 gc();
 assertEq(finalizeCount(), 0);
 i.exports = null;
 e = null;
 gc();
-assertEq(finalizeCount(), 1);
+assertEq(finalizeCount(), 0);
 f = null;
 gc();
-assertEq(finalizeCount(), 2);
+assertEq(finalizeCount(), 1);
 i = null;
 gc();
-assertEq(finalizeCount(), 2);
+assertEq(finalizeCount(), 1);
 t = null;
 gc();
-assertEq(finalizeCount(), 4);
+assertEq(finalizeCount(), 3);
 
 // Null elements shouldn't keep anything alive.
 resetFinalizeCount();
 var i = wasmEvalText(`(module (table 2 anyfunc) (export "tbl" table) ${caller})`);
 var e = i.exports;
 var t = e.tbl;
 i.edge = makeFinalizeObserver();
-e.edge = makeFinalizeObserver();
 t.edge = makeFinalizeObserver();
 gc();
 assertEq(finalizeCount(), 0);
 i.exports = null;
 e = null;
 gc();
+assertEq(finalizeCount(), 0);
+i = null;
+gc();
 assertEq(finalizeCount(), 1);
-i = null;
+t = null;
 gc();
 assertEq(finalizeCount(), 2);
-t = null;
-gc();
-assertEq(finalizeCount(), 3);
 
 // Before initialization, a table is not bound to any instance.
 resetFinalizeCount();
 var i = wasmEvalText(`(module (func $f0 (result i32) (i32.const 0)) (export "f0" $f0))`);
 var t = new Table({initial:4, element:"anyfunc"});
 i.edge = makeFinalizeObserver();
 t.edge = makeFinalizeObserver();
 gc();
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -2375,19 +2375,17 @@ class MOZ_STACK_CLASS ModuleValidator
         asmJSMetadata_->srcLengthWithRightBrace = endAfterCurly - asmJSMetadata_->srcStart;
 
         // asm.js does not have any wasm bytecode to save; view-source is
         // provided through the ScriptSource.
         SharedBytes bytes = js_new<ShareableBytes>();
         if (!bytes)
             return nullptr;
 
-        return mg_.finish(*bytes,
-                          DataSegmentVector(),
-                          NameInBytecodeVector());
+        return mg_.finish(*bytes);
     }
 };
 
 /*****************************************************************************/
 // Numeric literal utilities
 
 static bool
 IsNumericNonFloatLiteral(ParseNode* pn)
--- a/js/src/wasm/WasmBinaryConstants.h
+++ b/js/src/wasm/WasmBinaryConstants.h
@@ -26,17 +26,17 @@ namespace wasm {
 
 static const uint32_t MagicNumber        = 0x6d736100; // "\0asm"
 static const uint32_t EncodingVersion    = 0x0d;
 
 static const char NameSectionName[]      = "name";
 
 enum class SectionId
 {
-    UserDefined                          = 0,
+    Custom                               = 0,
     Type                                 = 1,
     Import                               = 2,
     Function                             = 3,
     Table                                = 4,
     Memory                               = 5,
     Global                               = 6,
     Export                               = 7,
     Start                                = 8,
--- a/js/src/wasm/WasmBinaryToAST.cpp
+++ b/js/src/wasm/WasmBinaryToAST.cpp
@@ -1807,20 +1807,20 @@ AstDecodeEnvironment(AstDecodeContext& c
 
     if (!AstCreateElems(c))
         return false;
 
     return true;
 }
 
 static bool
-AstDecodeCodeSection(AstDecodeContext &c)
+AstDecodeCodeSection(AstDecodeContext& c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(SectionId::Code, &sectionStart, &sectionSize, "code"))
+    if (!c.d.startSection(SectionId::Code, &c.env(), &sectionStart, &sectionSize, "code"))
         return false;
 
     if (sectionStart == Decoder::NotStarted) {
         if (c.env().numFuncDefs() != 0)
             return c.d.fail("expected function bodies");
 
         return true;
     }
@@ -1845,25 +1845,24 @@ AstDecodeCodeSection(AstDecodeContext &c
 
     return true;
 }
 
 // Number of bytes to display in a single fragment of a data section (per line).
 static const size_t WRAP_DATA_BYTES = 30;
 
 static bool
-AstDecodeDataSection(AstDecodeContext &c)
+AstDecodeModuleTail(AstDecodeContext& c)
 {
     MOZ_ASSERT(c.module().memories().length() <= 1, "at most one memory in MVP");
 
-    DataSegmentVector segments;
-    if (!DecodeDataSection(c.d, c.env(), &segments))
+    if (!DecodeModuleTail(c.d, &c.env()))
         return false;
 
-    for (DataSegment& s : segments) {
+    for (DataSegment& s : c.env().dataSegments) {
         const uint8_t* src = c.d.begin() + s.bytecodeOffset;
         char16_t* buffer = static_cast<char16_t*>(c.lifo.alloc(s.length * sizeof(char16_t)));
         for (size_t i = 0; i < s.length; i++)
             buffer[i] = src[i];
 
         AstExpr* offset = ToAstExpr(c, s.offset);
         if (!offset)
             return false;
@@ -1892,18 +1891,17 @@ wasm::BinaryToAst(JSContext* cx, const u
         return false;
 
     UniqueChars error;
     Decoder d(bytes, bytes + length, &error);
     AstDecodeContext c(cx, lifo, d, *result, true);
 
     if (!AstDecodeEnvironment(c) ||
         !AstDecodeCodeSection(c) ||
-        !AstDecodeDataSection(c) ||
-        !DecodeUnknownSections(c.d))
+        !AstDecodeModuleTail(c))
     {
         if (error) {
             JS_ReportErrorNumberASCII(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
                                       error.get());
             return false;
         }
         ReportOutOfMemory(c.cx);
         return false;
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -451,16 +451,17 @@ Metadata::serializedSize() const
            SerializedPodVectorSize(tables) +
            SerializedPodVectorSize(memoryAccesses) +
            SerializedPodVectorSize(memoryPatches) +
            SerializedPodVectorSize(boundsChecks) +
            SerializedPodVectorSize(codeRanges) +
            SerializedPodVectorSize(callSites) +
            SerializedPodVectorSize(callThunks) +
            SerializedPodVectorSize(funcNames) +
+           SerializedPodVectorSize(customSections) +
            filename.serializedSize();
 }
 
 uint8_t*
 Metadata::serialize(uint8_t* cursor) const
 {
     cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
     cursor = SerializeVector(cursor, funcImports);
@@ -470,16 +471,17 @@ Metadata::serialize(uint8_t* cursor) con
     cursor = SerializePodVector(cursor, tables);
     cursor = SerializePodVector(cursor, memoryAccesses);
     cursor = SerializePodVector(cursor, memoryPatches);
     cursor = SerializePodVector(cursor, boundsChecks);
     cursor = SerializePodVector(cursor, codeRanges);
     cursor = SerializePodVector(cursor, callSites);
     cursor = SerializePodVector(cursor, callThunks);
     cursor = SerializePodVector(cursor, funcNames);
+    cursor = SerializePodVector(cursor, customSections);
     cursor = filename.serialize(cursor);
     return cursor;
 }
 
 /* static */ const uint8_t*
 Metadata::deserialize(const uint8_t* cursor)
 {
     (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) &&
@@ -490,16 +492,17 @@ Metadata::deserialize(const uint8_t* cur
     (cursor = DeserializePodVector(cursor, &tables)) &&
     (cursor = DeserializePodVector(cursor, &memoryAccesses)) &&
     (cursor = DeserializePodVector(cursor, &memoryPatches)) &&
     (cursor = DeserializePodVector(cursor, &boundsChecks)) &&
     (cursor = DeserializePodVector(cursor, &codeRanges)) &&
     (cursor = DeserializePodVector(cursor, &callSites)) &&
     (cursor = DeserializePodVector(cursor, &callThunks)) &&
     (cursor = DeserializePodVector(cursor, &funcNames)) &&
+    (cursor = DeserializePodVector(cursor, &customSections)) &&
     (cursor = filename.deserialize(cursor));
     return cursor;
 }
 
 size_t
 Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return SizeOfVectorExcludingThis(funcImports, mallocSizeOf) +
@@ -509,16 +512,17 @@ Metadata::sizeOfExcludingThis(MallocSize
            tables.sizeOfExcludingThis(mallocSizeOf) +
            memoryAccesses.sizeOfExcludingThis(mallocSizeOf) +
            memoryPatches.sizeOfExcludingThis(mallocSizeOf) +
            boundsChecks.sizeOfExcludingThis(mallocSizeOf) +
            codeRanges.sizeOfExcludingThis(mallocSizeOf) +
            callSites.sizeOfExcludingThis(mallocSizeOf) +
            callThunks.sizeOfExcludingThis(mallocSizeOf) +
            funcNames.sizeOfExcludingThis(mallocSizeOf) +
+           customSections.sizeOfExcludingThis(mallocSizeOf) +
            filename.sizeOfExcludingThis(mallocSizeOf);
 }
 
 struct ProjectFuncIndex
 {
     const FuncExportVector& funcExports;
 
     explicit ProjectFuncIndex(const FuncExportVector& funcExports)
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -392,22 +392,42 @@ UsesMemory(MemoryUsage memoryUsage)
 // The presence of NameInBytecode implies that bytecode has been kept.
 
 struct NameInBytecode
 {
     uint32_t offset;
     uint32_t length;
 
     NameInBytecode() = default;
-    NameInBytecode(uint32_t offset, uint32_t length) : offset(offset), length(length) {}
+    NameInBytecode(uint32_t offset, uint32_t length)
+      : offset(offset), length(length)
+    {}
 };
 
 typedef Vector<NameInBytecode, 0, SystemAllocPolicy> NameInBytecodeVector;
 typedef Vector<char16_t, 64> TwoByteName;
 
+// CustomSection represents a custom section in the bytecode which can be
+// extracted via Module.customSections. The (offset, length) pair does not
+// include the custom section name.
+
+struct CustomSection
+{
+    NameInBytecode name;
+    uint32_t offset;
+    uint32_t length;
+
+    CustomSection() = default;
+    CustomSection(NameInBytecode name, uint32_t offset, uint32_t length)
+      : name(name), offset(offset), length(length)
+    {}
+};
+
+typedef Vector<CustomSection, 0, SystemAllocPolicy> CustomSectionVector;
+
 // Metadata holds all the data that is needed to describe compiled wasm code
 // at runtime (as opposed to data that is only used to statically link or
 // instantiate a module).
 //
 // Metadata is built incrementally by ModuleGenerator and then shared immutably
 // between modules.
 
 struct MetadataCacheablePod
@@ -440,16 +460,17 @@ struct Metadata : ShareableBase<Metadata
     TableDescVector       tables;
     MemoryAccessVector    memoryAccesses;
     MemoryPatchVector     memoryPatches;
     BoundsCheckVector     boundsChecks;
     CodeRangeVector       codeRanges;
     CallSiteVector        callSites;
     CallThunkVector       callThunks;
     NameInBytecodeVector  funcNames;
+    CustomSectionVector   customSections;
     CacheableChars        filename;
 
     bool usesMemory() const { return UsesMemory(memoryUsage); }
     bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; }
 
     const FuncExport& lookupFuncExport(uint32_t funcIndex) const;
 
     // AsmJSMetadata derives Metadata iff isAsmJS(). Mostly this distinction is
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -55,25 +55,24 @@ DecodeFunctionBody(Decoder& d, ModuleGen
     if (!fg.bytes().resize(bodySize))
         return false;
 
     memcpy(fg.bytes().begin(), bodyBegin, bodySize);
 
     return mg.finishFuncDef(funcIndex, &fg);
 }
 
-// Section decoding.
 static bool
 DecodeCodeSection(Decoder& d, ModuleGenerator& mg)
 {
-    if (!mg.startFuncDefs())
+    uint32_t sectionStart, sectionSize;
+    if (!d.startSection(SectionId::Code, &mg.mutableEnv(), &sectionStart, &sectionSize, "code"))
         return false;
 
-    uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Code, &sectionStart, &sectionSize, "code"))
+    if (!mg.startFuncDefs())
         return false;
 
     if (sectionStart == Decoder::NotStarted) {
         if (mg.env().numFuncDefs() != 0)
             return d.fail("expected function bodies");
 
         return mg.finishFuncDefs();
     }
@@ -91,81 +90,16 @@ DecodeCodeSection(Decoder& d, ModuleGene
     }
 
     if (!d.finishSection(sectionStart, sectionSize, "code"))
         return false;
 
     return mg.finishFuncDefs();
 }
 
-static void
-MaybeDecodeNameSectionBody(Decoder& d, NameInBytecodeVector* pfuncNames)
-{
-    // For simplicity, ignore all failures, even OOM. Failure will simply result
-    // in the names section not being included for this module.
-
-    uint32_t numFuncNames;
-    if (!d.readVarU32(&numFuncNames))
-        return;
-
-    if (numFuncNames > MaxFuncs)
-        return;
-
-    // Use a local vector (and not pfuncNames) since it could result in a
-    // partially initialized result in case of failure in the middle.
-    NameInBytecodeVector funcNames;
-    if (!funcNames.resize(numFuncNames))
-        return;
-
-    for (uint32_t i = 0; i < numFuncNames; i++) {
-        uint32_t numBytes;
-        if (!d.readVarU32(&numBytes))
-            return;
-
-        NameInBytecode name;
-        name.offset = d.currentOffset();
-        name.length = numBytes;
-        funcNames[i] = name;
-
-        if (!d.readBytes(numBytes))
-            return;
-
-        // Skip local names for a function.
-        uint32_t numLocals;
-        if (!d.readVarU32(&numLocals))
-            return;
-        for (uint32_t j = 0; j < numLocals; j++) {
-            uint32_t numBytes;
-            if (!d.readVarU32(&numBytes))
-                return;
-            if (!d.readBytes(numBytes))
-                return;
-        }
-    }
-
-    *pfuncNames = Move(funcNames);
-}
-
-static bool
-DecodeNameSection(Decoder& d, NameInBytecodeVector* funcNames)
-{
-    uint32_t sectionStart, sectionSize;
-    if (!d.startUserDefinedSection(NameSectionName, &sectionStart, &sectionSize))
-        return false;
-    if (sectionStart == Decoder::NotStarted)
-        return true;
-
-    // Once started, user-defined sections do not report validation errors.
-
-    MaybeDecodeNameSectionBody(d, funcNames);
-
-    d.finishUserDefinedSection(sectionStart, sectionSize);
-    return true;
-}
-
 bool
 CompileArgs::initFromContext(ExclusiveContext* cx, ScriptedCaller&& scriptedCaller)
 {
     alwaysBaseline = cx->options().wasmAlwaysBaseline();
     this->scriptedCaller = Move(scriptedCaller);
     return assumptions.initBuildIdFromContext(cx);
 }
 
@@ -185,25 +119,15 @@ wasm::Compile(const ShareableBytes& byte
 
     ModuleGenerator mg;
     if (!mg.init(Move(env), args))
         return nullptr;
 
     if (!DecodeCodeSection(d, mg))
         return nullptr;
 
-    DataSegmentVector dataSegments;
-    if (!DecodeDataSection(d, mg.env(), &dataSegments))
-        return nullptr;
-
-    NameInBytecodeVector funcNames;
-    if (!DecodeNameSection(d, &funcNames))
-        return nullptr;
-
-    if (!DecodeUnknownSections(d))
+    if (!DecodeModuleTail(d, &mg.mutableEnv()))
         return nullptr;
 
     MOZ_ASSERT(!*error, "unreported error in decoding");
 
-    return mg.finish(bytecode,
-                     Move(dataSegments),
-                     Move(funcNames));
+    return mg.finish(bytecode);
 }
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -214,16 +214,24 @@ ModuleGenerator::init(UniqueModuleEnviro
         metadata_->filename = DuplicateString(args.scriptedCaller.filename.get());
         if (!metadata_->filename)
             return false;
     }
 
     return true;
 }
 
+ModuleEnvironment&
+ModuleGenerator::mutableEnv()
+{
+    // Mutation is not safe during parallel compilation.
+    MOZ_ASSERT(!startedFuncDefs_ || finishedFuncDefs_);
+    return *env_;
+}
+
 bool
 ModuleGenerator::finishOutstandingTask()
 {
     MOZ_ASSERT(parallel_);
 
     CompileTask* task = nullptr;
     {
         AutoLockHelperThreadState lock;
@@ -1036,18 +1044,17 @@ ModuleGenerator::initSigTableElems(uint3
     if (!env_->elemSegments.emplaceBack(tableIndex, offset, Move(elemFuncIndices)))
         return false;
 
     env_->elemSegments.back().elemCodeRangeIndices = Move(codeRangeIndices);
     return true;
 }
 
 SharedModule
-ModuleGenerator::finish(const ShareableBytes& bytecode, DataSegmentVector&& dataSegments,
-                        NameInBytecodeVector&& funcNames)
+ModuleGenerator::finish(const ShareableBytes& bytecode)
 {
     MOZ_ASSERT(!activeFuncDef_);
     MOZ_ASSERT(finishedFuncDefs_);
 
     if (!finishFuncExports())
         return nullptr;
 
     if (!finishCodegen())
@@ -1074,29 +1081,29 @@ ModuleGenerator::finish(const ShareableB
     // Zero the padding, since we used resizeUninitialized above.
     memset(code.begin() + bytesNeeded, 0, padding);
 
     // Convert the CallSiteAndTargetVector (needed during generation) to a
     // CallSiteVector (what is stored in the Module).
     if (!metadata_->callSites.appendAll(masm_.callSites()))
         return nullptr;
 
-    metadata_->funcNames = Move(funcNames);
-
     // The MacroAssembler has accumulated all the memory accesses during codegen.
     metadata_->memoryAccesses = masm_.extractMemoryAccesses();
     metadata_->memoryPatches = masm_.extractMemoryPatches();
     metadata_->boundsChecks = masm_.extractBoundsChecks();
 
     // Copy over data from the ModuleEnvironment.
     metadata_->memoryUsage = env_->memoryUsage;
     metadata_->minMemoryLength = env_->minMemoryLength;
     metadata_->maxMemoryLength = env_->maxMemoryLength;
     metadata_->tables = Move(env_->tables);
     metadata_->globals = Move(env_->globals);
+    metadata_->funcNames = Move(env_->funcNames);
+    metadata_->customSections = Move(env_->customSections);
 
     // These Vectors can get large and the excess capacity can be significant,
     // so realloc them down to size.
     metadata_->memoryAccesses.podResizeToFit();
     metadata_->memoryPatches.podResizeToFit();
     metadata_->boundsChecks.podResizeToFit();
     metadata_->codeRanges.podResizeToFit();
     metadata_->callSites.podResizeToFit();
@@ -1119,17 +1126,17 @@ ModuleGenerator::finish(const ShareableB
     if (!finishLinkData(code))
         return nullptr;
 
     return SharedModule(js_new<Module>(Move(assumptions_),
                                        Move(code),
                                        Move(linkData_),
                                        Move(env_->imports),
                                        Move(env_->exports),
-                                       Move(dataSegments),
+                                       Move(env_->dataSegments),
                                        Move(env_->elemSegments),
                                        *metadata_,
                                        bytecode));
 }
 
 bool
 wasm::CompileFunction(CompileTask* task)
 {
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -226,16 +226,17 @@ class MOZ_STACK_CLASS ModuleGenerator
   public:
     explicit ModuleGenerator();
     ~ModuleGenerator();
 
     MOZ_MUST_USE bool init(UniqueModuleEnvironment env, const CompileArgs& args,
                            Metadata* maybeAsmJSMetadata = nullptr);
 
     const ModuleEnvironment& env() const { return *env_; }
+    ModuleEnvironment& mutableEnv();
 
     bool isAsmJS() const { return metadata_->kind == ModuleKind::AsmJS; }
     jit::MacroAssembler& masm() { return masm_; }
 
     // Memory:
     bool usesMemory() const { return env_->usesMemory(); }
     uint32_t minMemoryLength() const { return env_->minMemoryLength; }
 
@@ -264,21 +265,18 @@ class MOZ_STACK_CLASS ModuleGenerator
     MOZ_MUST_USE bool initImport(uint32_t funcIndex, uint32_t sigIndex);
     MOZ_MUST_USE bool initSigTableLength(uint32_t sigIndex, uint32_t length);
     MOZ_MUST_USE bool initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices);
     void initMemoryUsage(MemoryUsage memoryUsage);
     void bumpMinMemoryLength(uint32_t newMinMemoryLength);
     MOZ_MUST_USE bool addGlobal(ValType type, bool isConst, uint32_t* index);
     MOZ_MUST_USE bool addExport(CacheableChars&& fieldChars, uint32_t funcIndex);
 
-    // Finish compilation, provided the list of imports and source bytecode.
-    // Both these Vectors may be empty (viz., b/c asm.js does different things
-    // for imports and source).
-    SharedModule finish(const ShareableBytes& bytecode, DataSegmentVector&& dataSegments,
-                        NameInBytecodeVector&& funcNames);
+    // Finish compilation of the given bytecode.
+    SharedModule finish(const ShareableBytes& bytecode);
 };
 
 // A FunctionGenerator encapsulates the generation of a single function body.
 // ModuleGenerator::startFunc must be called after construction and before doing
 // anything else. After the body is complete, ModuleGenerator::finishFunc must
 // be called before the FunctionGenerator is destroyed and the next function is
 // started.
 
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -15,16 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "wasm/WasmJS.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/RangedPtr.h"
 
 #include "jsprf.h"
 
 #include "builtin/Promise.h"
 #include "jit/JitOptions.h"
 #include "vm/Interpreter.h"
 #include "vm/String.h"
 #include "wasm/WasmCompile.h"
@@ -39,16 +40,17 @@
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 using mozilla::CheckedInt;
 using mozilla::IsNaN;
 using mozilla::IsSame;
 using mozilla::Nothing;
+using mozilla::RangedPtr;
 
 bool
 wasm::HasCompilerSupport(ExclusiveContext* cx)
 {
     if (gc::SystemPageSize() > wasm::PageSize)
         return false;
 
     if (!cx->jitSupportsFloatingPoint())
@@ -498,16 +500,17 @@ const JSPropertySpec WasmModuleObject::p
 
 const JSFunctionSpec WasmModuleObject::methods[] =
 { JS_FS_END };
 
 const JSFunctionSpec WasmModuleObject::static_methods[] =
 {
     JS_FN("imports", WasmModuleObject::imports, 1, 0),
     JS_FN("exports", WasmModuleObject::exports, 1, 0),
+    JS_FN("customSections", WasmModuleObject::customSections, 2, 0),
     JS_FS_END
 };
 
 /* static */ void
 WasmModuleObject::finalize(FreeOp* fop, JSObject* obj)
 {
     obj->as<WasmModuleObject>().module().Release();
 }
@@ -684,16 +687,68 @@ WasmModuleObject::exports(JSContext* cx,
     JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
     if (!arr)
         return false;
 
     args.rval().setObject(*arr);
     return true;
 }
 
+/* static */ bool
+WasmModuleObject::customSections(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    Module* module;
+    if (!GetModuleArg(cx, args, "WebAssembly.Module.customSections", &module))
+        return false;
+
+    Vector<char, 8> name(cx);
+    {
+        RootedString str(cx, ToString(cx, args.get(1)));
+        if (!str)
+            return false;
+
+        Rooted<JSFlatString*> flat(cx, str->ensureFlat(cx));
+        if (!flat)
+            return false;
+
+        if (!name.initLengthUninitialized(JS::GetDeflatedUTF8StringLength(flat)))
+            return false;
+
+        JS::DeflateStringToUTF8Buffer(flat, RangedPtr<char>(name.begin(), name.length()));
+    }
+
+    const uint8_t* bytecode = module->bytecode().begin();
+
+    AutoValueVector elems(cx);
+    RootedArrayBufferObject buf(cx);
+    for (const CustomSection& sec : module->metadata().customSections) {
+        if (name.length() != sec.name.length)
+            continue;
+        if (memcmp(name.begin(), bytecode + sec.name.offset, name.length()))
+            continue;
+
+        buf = ArrayBufferObject::create(cx, sec.length);
+        if (!buf)
+            return false;
+
+        memcpy(buf->dataPointer(), bytecode + sec.offset, sec.length);
+        if (!elems.append(ObjectValue(*buf)))
+            return false;
+    }
+
+    JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
+    if (!arr)
+        return false;
+
+    args.rval().setObject(*arr);
+    return true;
+}
+
 /* static */ WasmModuleObject*
 WasmModuleObject::create(ExclusiveContext* cx, Module& module, HandleObject proto)
 {
     AutoSetNewObjectMetadata metadata(cx);
     auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
     if (!obj)
         return nullptr;
 
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -119,16 +119,17 @@ InitWebAssemblyClass(JSContext* cx, Hand
 
 class WasmModuleObject : public NativeObject
 {
     static const unsigned MODULE_SLOT = 0;
     static const ClassOps classOps_;
     static void finalize(FreeOp* fop, JSObject* obj);
     static bool imports(JSContext* cx, unsigned argc, Value* vp);
     static bool exports(JSContext* cx, unsigned argc, Value* vp);
+    static bool customSections(JSContext* cx, unsigned argc, Value* vp);
 
   public:
     static const unsigned RESERVED_SLOTS = 1;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static const JSFunctionSpec static_methods[];
     static bool construct(JSContext*, unsigned, Value*);
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -814,17 +814,20 @@ CreateExportObject(JSContext* cx,
     if (metadata.isAsmJS() && exports.length() == 1 && strlen(exports[0].fieldName()) == 0) {
         RootedValue val(cx);
         if (!GetFunctionExport(cx, instanceObj, funcImports, exports[0], &val))
             return false;
         exportObj.set(&val.toObject());
         return true;
     }
 
-    exportObj.set(JS_NewPlainObject(cx));
+    if (metadata.isAsmJS())
+        exportObj.set(NewBuiltinClassInstance<PlainObject>(cx));
+    else
+        exportObj.set(NewObjectWithGivenProto<PlainObject>(cx, nullptr));
     if (!exportObj)
         return false;
 
     for (const Export& exp : exports) {
         JSAtom* atom = AtomizeUTF8Chars(cx, exp.fieldName(), strlen(exp.fieldName()));
         if (!atom)
             return false;
 
@@ -846,16 +849,21 @@ CreateExportObject(JSContext* cx,
                 return false;
             break;
         }
 
         if (!JS_DefinePropertyById(cx, exportObj, id, val, JSPROP_ENUMERATE))
             return false;
     }
 
+    if (!metadata.isAsmJS()) {
+        if (!JS_FreezeObject(cx, exportObj))
+            return false;
+    }
+
     return true;
 }
 
 bool
 Module::instantiate(JSContext* cx,
                     Handle<FunctionVector> funcImports,
                     HandleWasmTableObject tableImport,
                     HandleWasmMemoryObject memoryImport,
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -132,16 +132,17 @@ class Module : public JS::WasmModule
         metadata_(&metadata),
         bytecode_(&bytecode)
     {}
     ~Module() override { /* Note: can be called on any thread */ }
 
     const Metadata& metadata() const { return *metadata_; }
     const ImportVector& imports() const { return imports_; }
     const ExportVector& exports() const { return exports_; }
+    const Bytes& bytecode() const { return bytecode_->bytes; }
 
     // Instantiate this module with the given imports:
 
     bool instantiate(JSContext* cx,
                      Handle<FunctionVector> funcImports,
                      HandleWasmTableObject tableImport,
                      HandleWasmMemoryObject memoryImport,
                      const ValVector& globalImports,
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -53,16 +53,158 @@ Decoder::fail(UniqueChars msg)
     UniqueChars strWithOffset(JS_smprintf("at offset %" PRIuSIZE ": %s", currentOffset(), msg.get()));
     if (!strWithOffset)
         return false;
 
     *error_ = Move(strWithOffset);
     return false;
 }
 
+bool
+Decoder::startSection(SectionId id, ModuleEnvironment* env, uint32_t* sectionStart,
+                      uint32_t* sectionSize, const char* sectionName)
+{
+    // Record state at beginning of section to allow rewinding to this point
+    // if, after skipping through several custom sections, we don't find the
+    // section 'id'.
+    const uint8_t* const initialCur = cur_;
+    const size_t initialCustomSectionsLength = env->customSections.length();
+
+    // Maintain a pointer to the current section that gets updated as custom
+    // sections are skipped.
+    const uint8_t* currentSectionStart = cur_;
+
+    // Only start a section with 'id', skipping any custom sections before it.
+
+    uint32_t idValue;
+    if (!readVarU32(&idValue))
+        goto rewind;
+
+    while (idValue != uint32_t(id)) {
+        if (idValue != uint32_t(SectionId::Custom))
+            goto rewind;
+
+        // Rewind to the beginning of the current section since this is what
+        // skipCustomSection() assumes.
+        cur_ = currentSectionStart;
+        if (!skipCustomSection(env))
+            return false;
+
+        // Having successfully skipped a custom section, consider the next
+        // section.
+        currentSectionStart = cur_;
+        if (!readVarU32(&idValue))
+            goto rewind;
+    }
+
+    // Found it, now start the section.
+
+    if (!readVarU32(sectionSize) || bytesRemain() < *sectionSize)
+        goto fail;
+
+    *sectionStart = cur_ - beg_;
+    return true;
+
+  rewind:
+    cur_ = initialCur;
+    env->customSections.shrinkTo(initialCustomSectionsLength);
+    *sectionStart = NotStarted;
+    return true;
+
+  fail:
+    return fail("failed to start %s section", sectionName);
+}
+
+bool
+Decoder::finishSection(uint32_t sectionStart, uint32_t sectionSize, const char* sectionName)
+{
+    if (sectionSize != (cur_ - beg_) - sectionStart)
+        return fail("byte size mismatch in %s section", sectionName);
+    return true;
+}
+
+bool
+Decoder::startCustomSection(const char* expected, size_t expectedLength, ModuleEnvironment* env,
+                            uint32_t* sectionStart, uint32_t* sectionSize)
+{
+    // Record state at beginning of section to allow rewinding to this point
+    // if, after skipping through several custom sections, we don't find the
+    // section 'id'.
+    const uint8_t* const initialCur = cur_;
+    const size_t initialCustomSectionsLength = env->customSections.length();
+
+    while (true) {
+        // Try to start a custom section. If we can't, rewind to the beginning
+        // since we may have skipped several custom sections already looking for
+        // 'expected'.
+        if (!startSection(SectionId::Custom, env, sectionStart, sectionSize, "custom"))
+            return false;
+        if (*sectionStart == NotStarted)
+            goto rewind;
+
+        NameInBytecode name;
+        if (!readVarU32(&name.length) || name.length > bytesRemain())
+            goto fail;
+
+        name.offset = currentOffset();
+        uint32_t payloadOffset = name.offset + name.length;
+        uint32_t payloadEnd = *sectionStart + *sectionSize;
+        if (payloadOffset > payloadEnd)
+            goto fail;
+
+        // Now that we have a valid custom section, record its offsets in the
+        // metadata which can be queried by the user via Module.customSections.
+        // Note: after an entry is appended, it may be popped if this loop or
+        // the loop in startSection needs to rewind.
+        if (!env->customSections.emplaceBack(name, payloadOffset, payloadEnd - payloadOffset))
+            return false;
+
+        // If this is the expected custom section, we're done.
+        if (!expected || (expectedLength == name.length && !memcmp(cur_, expected, name.length))) {
+            cur_ += name.length;
+            return true;
+        }
+
+        // Otherwise, blindly skip the custom section and keep looking.
+        finishCustomSection(*sectionStart, *sectionSize);
+    }
+    MOZ_CRASH("unreachable");
+
+  rewind:
+    cur_ = initialCur;
+    env->customSections.shrinkTo(initialCustomSectionsLength);
+    return true;
+
+  fail:
+    return fail("failed to start custom section");
+}
+
+void
+Decoder::finishCustomSection(uint32_t sectionStart, uint32_t sectionSize)
+{
+    MOZ_ASSERT(cur_ >= beg_);
+    MOZ_ASSERT(cur_ <= end_);
+    cur_ = (beg_ + sectionStart) + sectionSize;
+    MOZ_ASSERT(cur_ <= end_);
+    clearError();
+}
+
+bool
+Decoder::skipCustomSection(ModuleEnvironment* env)
+{
+    uint32_t sectionStart, sectionSize;
+    if (!startCustomSection(nullptr, 0, env, &sectionStart, &sectionSize))
+        return false;
+    if (sectionStart == NotStarted)
+        return fail("expected custom section");
+
+    finishCustomSection(sectionStart, sectionSize);
+    return true;
+}
+
 // Misc helpers.
 
 bool
 wasm::EncodeLocalEntries(Encoder& e, const ValTypeVector& locals)
 {
     uint32_t numLocalEntries = 0;
     ValType prev = ValType(TypeCode::Limit);
     for (ValType t : locals) {
@@ -566,32 +708,32 @@ DecodePreamble(Decoder& d)
     if (!d.readFixedU32(&u32) || u32 != EncodingVersion)
         return d.fail("binary version 0x%" PRIx32 " does not match expected version 0x%" PRIx32,
                       u32, EncodingVersion);
 
     return true;
 }
 
 static bool
-DecodeTypeSection(Decoder& d, SigWithIdVector* sigs)
+DecodeTypeSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Type, &sectionStart, &sectionSize, "type"))
+    if (!d.startSection(SectionId::Type, env, &sectionStart, &sectionSize, "type"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numSigs;
     if (!d.readVarU32(&numSigs))
         return d.fail("expected number of signatures");
 
     if (numSigs > MaxSigs)
         return d.fail("too many signatures");
 
-    if (!sigs->resize(numSigs))
+    if (!env->sigs.resize(numSigs))
         return false;
 
     for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) {
         uint32_t form;
         if (!d.readVarU32(&form) || form != uint32_t(TypeCode::Func))
             return d.fail("expected function form");
 
         uint32_t numArgs;
@@ -622,17 +764,17 @@ DecodeTypeSection(Decoder& d, SigWithIdV
         if (numRets == 1) {
             ValType type;
             if (!DecodeValType(d, ModuleKind::Wasm, &type))
                 return false;
 
             result = ToExprType(type);
         }
 
-        (*sigs)[sigIndex] = Sig(Move(args), result);
+        env->sigs[sigIndex] = Sig(Move(args), result);
     }
 
     if (!d.finishSection(sectionStart, sectionSize, "type"))
         return false;
 
     return true;
 }
 
@@ -847,17 +989,17 @@ DecodeImport(Decoder& d, ModuleEnvironme
 
     return env->imports.emplaceBack(Move(moduleName), Move(funcName), importKind);
 }
 
 static bool
 DecodeImportSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Import, &sectionStart, &sectionSize, "import"))
+    if (!d.startSection(SectionId::Import, env, &sectionStart, &sectionSize, "import"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numImports;
     if (!d.readVarU32(&numImports))
         return d.fail("failed to read number of imports");
 
@@ -878,17 +1020,17 @@ DecodeImportSection(Decoder& d, ModuleEn
 
     return true;
 }
 
 static bool
 DecodeFunctionSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Function, &sectionStart, &sectionSize, "function"))
+    if (!d.startSection(SectionId::Function, env, &sectionStart, &sectionSize, "function"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numDefs;
     if (!d.readVarU32(&numDefs))
         return d.fail("expected number of function definitions");
 
@@ -909,45 +1051,45 @@ DecodeFunctionSection(Decoder& d, Module
 
     if (!d.finishSection(sectionStart, sectionSize, "function"))
         return false;
 
     return true;
 }
 
 static bool
-DecodeTableSection(Decoder& d, TableDescVector* tables)
+DecodeTableSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Table, &sectionStart, &sectionSize, "table"))
+    if (!d.startSection(SectionId::Table, env, &sectionStart, &sectionSize, "table"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numTables;
     if (!d.readVarU32(&numTables))
         return d.fail("failed to read number of tables");
 
     if (numTables != 1)
         return d.fail("the number of tables must be exactly one");
 
-    if (!DecodeTableLimits(d, tables))
+    if (!DecodeTableLimits(d, &env->tables))
         return false;
 
     if (!d.finishSection(sectionStart, sectionSize, "table"))
         return false;
 
     return true;
 }
 
 static bool
 DecodeMemorySection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Memory, &sectionStart, &sectionSize, "memory"))
+    if (!d.startSection(SectionId::Memory, env, &sectionStart, &sectionSize, "memory"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numMemories;
     if (!d.readVarU32(&numMemories))
         return d.fail("failed to read number of memories");
 
@@ -1022,47 +1164,47 @@ DecodeInitializerExpression(Decoder& d, 
     uint16_t end;
     if (!d.readOp(&end) || end != uint16_t(Op::End))
         return d.fail("failed to read end of initializer expression");
 
     return true;
 }
 
 static bool
-DecodeGlobalSection(Decoder& d, GlobalDescVector* globals)
+DecodeGlobalSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Global, &sectionStart, &sectionSize, "global"))
+    if (!d.startSection(SectionId::Global, env, &sectionStart, &sectionSize, "global"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numDefs;
     if (!d.readVarU32(&numDefs))
         return d.fail("expected number of globals");
 
-    CheckedInt<uint32_t> numGlobals = globals->length();
+    CheckedInt<uint32_t> numGlobals = env->globals.length();
     numGlobals += numDefs;
     if (!numGlobals.isValid() || numGlobals.value() > MaxGlobals)
         return d.fail("too many globals");
 
-    if (!globals->reserve(numGlobals.value()))
+    if (!env->globals.reserve(numGlobals.value()))
         return false;
 
     for (uint32_t i = 0; i < numDefs; i++) {
         ValType type;
         bool isMutable;
         if (!DecodeGlobalType(d, &type, &isMutable))
             return false;
 
         InitExpr initializer;
-        if (!DecodeInitializerExpression(d, *globals, type, &initializer))
+        if (!DecodeInitializerExpression(d, env->globals, type, &initializer))
             return false;
 
-        globals->infallibleAppend(GlobalDesc(initializer, isMutable));
+        env->globals.infallibleAppend(GlobalDesc(initializer, isMutable));
     }
 
     if (!d.finishSection(sectionStart, sectionSize, "global"))
         return false;
 
     return true;
 }
 
@@ -1154,17 +1296,17 @@ DecodeExport(Decoder& d, ModuleEnvironme
 
     MOZ_CRASH("unreachable");
 }
 
 static bool
 DecodeExportSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Export, &sectionStart, &sectionSize, "export"))
+    if (!d.startSection(SectionId::Export, env, &sectionStart, &sectionSize, "export"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     CStringSet dupSet;
     if (!dupSet.init())
         return false;
 
@@ -1185,17 +1327,17 @@ DecodeExportSection(Decoder& d, ModuleEn
 
     return true;
 }
 
 static bool
 DecodeStartSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Start, &sectionStart, &sectionSize, "start"))
+    if (!d.startSection(SectionId::Start, env, &sectionStart, &sectionSize, "start"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t funcIndex;
     if (!d.readVarU32(&funcIndex))
         return d.fail("failed to read start func index");
 
@@ -1216,17 +1358,17 @@ DecodeStartSection(Decoder& d, ModuleEnv
 
     return true;
 }
 
 static bool
 DecodeElemSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Elem, &sectionStart, &sectionSize, "elem"))
+    if (!d.startSection(SectionId::Elem, env, &sectionStart, &sectionSize, "elem"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numSegments;
     if (!d.readVarU32(&numSegments))
         return d.fail("failed to read number of elem segments");
 
@@ -1274,32 +1416,32 @@ DecodeElemSection(Decoder& d, ModuleEnvi
 }
 
 bool
 wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env)
 {
     if (!DecodePreamble(d))
         return false;
 
-    if (!DecodeTypeSection(d, &env->sigs))
+    if (!DecodeTypeSection(d, env))
         return false;
 
     if (!DecodeImportSection(d, env))
         return false;
 
     if (!DecodeFunctionSection(d, env))
         return false;
 
-    if (!DecodeTableSection(d, &env->tables))
+    if (!DecodeTableSection(d, env))
         return false;
 
     if (!DecodeMemorySection(d, env))
         return false;
 
-    if (!DecodeGlobalSection(d, &env->globals))
+    if (!DecodeGlobalSection(d, env))
         return false;
 
     if (!DecodeExportSection(d, env))
         return false;
 
     if (!DecodeStartSection(d, env))
         return false;
 
@@ -1326,56 +1468,56 @@ DecodeFunctionBody(Decoder& d, const Mod
 
     if (d.currentPosition() != bodyBegin + bodySize)
         return d.fail("function body length mismatch");
 
     return true;
 }
 
 static bool
-DecodeCodeSection(Decoder& d, const ModuleEnvironment& env)
+DecodeCodeSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Code, &sectionStart, &sectionSize, "code"))
+    if (!d.startSection(SectionId::Code, env, &sectionStart, &sectionSize, "code"))
         return false;
 
     if (sectionStart == Decoder::NotStarted) {
-        if (env.numFuncDefs() != 0)
+        if (env->numFuncDefs() != 0)
             return d.fail("expected function bodies");
         return true;
     }
 
     uint32_t numFuncDefs;
     if (!d.readVarU32(&numFuncDefs))
         return d.fail("expected function body count");
 
-    if (numFuncDefs != env.numFuncDefs())
+    if (numFuncDefs != env->numFuncDefs())
         return d.fail("function body count does not match function signature count");
 
     for (uint32_t funcDefIndex = 0; funcDefIndex < numFuncDefs; funcDefIndex++) {
-        if (!DecodeFunctionBody(d, env, env.numFuncImports() + funcDefIndex))
+        if (!DecodeFunctionBody(d, *env, env->numFuncImports() + funcDefIndex))
             return false;
     }
 
     if (!d.finishSection(sectionStart, sectionSize, "code"))
         return false;
 
     return true;
 }
 
-bool
-wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* segments)
+static bool
+DecodeDataSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Data, &sectionStart, &sectionSize, "data"))
+    if (!d.startSection(SectionId::Data, env, &sectionStart, &sectionSize, "data"))
         return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
-    if (!env.usesMemory())
+    if (!env->usesMemory())
         return d.fail("data section requires a memory section");
 
     uint32_t numSegments;
     if (!d.readVarU32(&numSegments))
         return d.fail("failed to read number of data segments");
 
     if (numSegments > MaxDataSegments)
         return d.fail("too many data segments");
@@ -1384,42 +1526,113 @@ wasm::DecodeDataSection(Decoder& d, cons
         uint32_t linearMemoryIndex;
         if (!d.readVarU32(&linearMemoryIndex))
             return d.fail("expected linear memory index");
 
         if (linearMemoryIndex != 0)
             return d.fail("linear memory index must currently be 0");
 
         DataSegment seg;
-        if (!DecodeInitializerExpression(d, env.globals, ValType::I32, &seg.offset))
+        if (!DecodeInitializerExpression(d, env->globals, ValType::I32, &seg.offset))
             return false;
 
         if (!d.readVarU32(&seg.length))
             return d.fail("expected segment size");
 
         seg.bytecodeOffset = d.currentOffset();
 
         if (!d.readBytes(seg.length))
             return d.fail("data segment shorter than declared");
 
-        if (!segments->append(seg))
+        if (!env->dataSegments.append(seg))
             return false;
     }
 
     if (!d.finishSection(sectionStart, sectionSize, "data"))
         return false;
 
     return true;
 }
 
-bool
-wasm::DecodeUnknownSections(Decoder& d)
+static void
+MaybeDecodeNameSectionBody(Decoder& d, ModuleEnvironment* env)
 {
+    // For simplicity, ignore all failures, even OOM. Failure will simply result
+    // in the names section not being included for this module.
+
+    uint32_t numFuncNames;
+    if (!d.readVarU32(&numFuncNames))
+        return;
+
+    if (numFuncNames > MaxFuncs)
+        return;
+
+    // Use a local vector (and not env->funcNames) since it could result in a
+    // partially initialized result in case of failure in the middle.
+    NameInBytecodeVector funcNames;
+    if (!funcNames.resize(numFuncNames))
+        return;
+
+    for (uint32_t i = 0; i < numFuncNames; i++) {
+        uint32_t numBytes;
+        if (!d.readVarU32(&numBytes))
+            return;
+
+        NameInBytecode name;
+        name.offset = d.currentOffset();
+        name.length = numBytes;
+        funcNames[i] = name;
+
+        if (!d.readBytes(numBytes))
+            return;
+
+        // Skip local names for a function.
+        uint32_t numLocals;
+        if (!d.readVarU32(&numLocals))
+            return;
+        for (uint32_t j = 0; j < numLocals; j++) {
+            uint32_t numBytes;
+            if (!d.readVarU32(&numBytes))
+                return;
+            if (!d.readBytes(numBytes))
+                return;
+        }
+    }
+
+    env->funcNames = Move(funcNames);
+}
+
+static bool
+DecodeNameSection(Decoder& d, ModuleEnvironment* env)
+{
+    uint32_t sectionStart, sectionSize;
+    if (!d.startCustomSection(NameSectionName, env, &sectionStart, &sectionSize))
+        return false;
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    // Once started, custom sections do not report validation errors.
+
+    MaybeDecodeNameSectionBody(d, env);
+
+    d.finishCustomSection(sectionStart, sectionSize);
+    return true;
+}
+
+bool
+wasm::DecodeModuleTail(Decoder& d, ModuleEnvironment* env)
+{
+    if (!DecodeDataSection(d, env))
+        return false;
+
+    if (!DecodeNameSection(d, env))
+        return false;
+
     while (!d.done()) {
-        if (!d.skipUserDefinedSection())
+        if (!d.skipCustomSection(env))
             return false;
     }
 
     return true;
 }
 
 // Validate algorithm.
 
@@ -1427,20 +1640,17 @@ bool
 wasm::Validate(const ShareableBytes& bytecode, UniqueChars* error)
 {
     Decoder d(bytecode.begin(), bytecode.end(), error);
 
     ModuleEnvironment env;
     if (!DecodeModuleEnvironment(d, &env))
         return false;
 
-    if (!DecodeCodeSection(d, env))
+    if (!DecodeCodeSection(d, &env))
         return false;
 
-    DataSegmentVector dataSegments;
-    if (!DecodeDataSection(d, env, &dataSegments))
+    if (!DecodeModuleTail(d, &env))
         return false;
 
-    if (!DecodeUnknownSections(d))
-        return false;
-
+    MOZ_ASSERT(!*error, "unreported error in decoding");
     return true;
 }
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -20,16 +20,92 @@
 #define wasm_validate_h
 
 #include "wasm/WasmCode.h"
 #include "wasm/WasmTypes.h"
 
 namespace js {
 namespace wasm {
 
+// ModuleEnvironment contains all the state necessary to validate, process or
+// render functions. It is created by decoding all the sections before the wasm
+// code section and then used immutably during. When compiling a module using a
+// ModuleGenerator, the ModuleEnvironment holds state shared between the
+// ModuleGenerator thread and background compile threads. All the threads
+// are given a read-only view of the ModuleEnvironment, thus preventing race
+// conditions.
+
+struct ModuleEnvironment
+{
+    ModuleKind                kind;
+    MemoryUsage               memoryUsage;
+    mozilla::Atomic<uint32_t> minMemoryLength;
+    Maybe<uint32_t>           maxMemoryLength;
+
+    SigWithIdVector           sigs;
+    SigWithIdPtrVector        funcSigs;
+    Uint32Vector              funcImportGlobalDataOffsets;
+    GlobalDescVector          globals;
+    TableDescVector           tables;
+    Uint32Vector              asmJSSigToTableIndex;
+    ImportVector              imports;
+    ExportVector              exports;
+    Maybe<uint32_t>           startFuncIndex;
+    ElemSegmentVector         elemSegments;
+    DataSegmentVector         dataSegments;
+    NameInBytecodeVector      funcNames;
+    CustomSectionVector       customSections;
+
+    explicit ModuleEnvironment(ModuleKind kind = ModuleKind::Wasm)
+      : kind(kind),
+        memoryUsage(MemoryUsage::None),
+        minMemoryLength(0)
+    {}
+
+    size_t numTables() const {
+        return tables.length();
+    }
+    size_t numSigs() const {
+        return sigs.length();
+    }
+    size_t numFuncs() const {
+        // asm.js pre-reserves a bunch of function index space which is
+        // incrementally filled in during function-body validation. Thus, there
+        // are a few possible interpretations of numFuncs() (total index space
+        // size vs.  exact number of imports/definitions encountered so far) and
+        // to simplify things we simply only define this quantity for wasm.
+        MOZ_ASSERT(!isAsmJS());
+        return funcSigs.length();
+    }
+    size_t numFuncDefs() const {
+        // asm.js overallocates the length of funcSigs and in general does not
+        // know the number of function definitions until it's done compiling.
+        MOZ_ASSERT(!isAsmJS());
+        return funcSigs.length() - funcImportGlobalDataOffsets.length();
+    }
+    size_t numFuncImports() const {
+        MOZ_ASSERT(!isAsmJS());
+        return funcImportGlobalDataOffsets.length();
+    }
+    bool usesMemory() const {
+        return UsesMemory(memoryUsage);
+    }
+    bool isAsmJS() const {
+        return kind == ModuleKind::AsmJS;
+    }
+    bool funcIsImport(uint32_t funcIndex) const {
+        return funcIndex < funcImportGlobalDataOffsets.length();
+    }
+    uint32_t funcIndexToSigIndex(uint32_t funcIndex) const {
+        return funcSigs[funcIndex] - sigs.begin();
+    }
+};
+
+typedef UniquePtr<ModuleEnvironment> UniqueModuleEnvironment;
+
 // The Encoder class appends bytes to the Bytes object it is given during
 // construction. The client is responsible for the Bytes's lifetime and must
 // keep the Bytes alive as long as the Encoder is used.
 
 class Encoder
 {
     Bytes& bytes_;
 
@@ -204,18 +280,16 @@ class Encoder
 
     // A "section" is a contiguous range of bytes that stores its own size so
     // that it may be trivially skipped without examining the contents. Sections
     // require backpatching since the size of the section is only known at the
     // end while the size's varU32 must be stored at the beginning. Immediately
     // after the section length is the string id of the section.
 
     MOZ_MUST_USE bool startSection(SectionId id, size_t* offset) {
-        MOZ_ASSERT(id != SectionId::UserDefined); // not supported yet
-
         return writeVarU32(uint32_t(id)) &&
                writePatchableVarU32(offset);
     }
     void finishSection(size_t offset) {
         return patchVarU32(offset, bytes_.length() - offset - varU32ByteLength(offset));
     }
 };
 
@@ -438,113 +512,44 @@ class Decoder
         return true;
     }
 
     // See "section" description in Encoder.
 
     static const uint32_t NotStarted = UINT32_MAX;
 
     MOZ_MUST_USE bool startSection(SectionId id,
-                                   uint32_t* startOffset,
-                                   uint32_t* size,
-                                   const char* sectionName)
-    {
-        const uint8_t* const before = cur_;
-        const uint8_t* beforeId = before;
-        uint32_t idValue;
-        if (!readVarU32(&idValue))
-            goto backup;
-        while (idValue != uint32_t(id)) {
-            if (idValue != uint32_t(SectionId::UserDefined))
-                goto backup;
-            // Rewind to the section id since skipUserDefinedSection expects it.
-            cur_ = beforeId;
-            if (!skipUserDefinedSection())
-                return false;
-            beforeId = cur_;
-            if (!readVarU32(&idValue))
-                goto backup;
-        }
-        if (!readVarU32(size))
-            goto fail;
-        if (bytesRemain() < *size)
-            goto fail;
-        *startOffset = cur_ - beg_;
-        return true;
-      backup:
-        cur_ = before;
-        *startOffset = NotStarted;
-        return true;
-      fail:
-        return fail("failed to start %s section", sectionName);
-    }
-    MOZ_MUST_USE bool finishSection(uint32_t startOffset, uint32_t size,
-                                    const char* sectionName)
-    {
-        if (size != (cur_ - beg_) - startOffset)
-            return fail("byte size mismatch in %s section", sectionName);
-        return true;
-    }
+                                   ModuleEnvironment* env,
+                                   uint32_t* sectionStart,
+                                   uint32_t* sectionSize,
+                                   const char* sectionName);
+    MOZ_MUST_USE bool finishSection(uint32_t sectionStart,
+                                    uint32_t sectionSize,
+                                    const char* sectionName);
+
+    // Custom sections do not cause validation errors unless the error is in
+    // the section header itself.
 
-    // "User sections" do not cause validation errors unless the error is in
-    // the user-defined section header itself.
-
-    MOZ_MUST_USE bool startUserDefinedSection(const char* expectedId,
-                                              size_t expectedIdSize,
-                                              uint32_t* sectionStart,
-                                              uint32_t* sectionSize)
+    MOZ_MUST_USE bool startCustomSection(const char* expected,
+                                         size_t expectedLength,
+                                         ModuleEnvironment* env,
+                                         uint32_t* sectionStart,
+                                         uint32_t* sectionSize);
+    template <size_t NameSizeWith0>
+    MOZ_MUST_USE bool startCustomSection(const char (&name)[NameSizeWith0],
+                                         ModuleEnvironment* env,
+                                         uint32_t* sectionStart,
+                                         uint32_t* sectionSize)
     {
-        const uint8_t* const before = cur_;
-        while (true) {
-            if (!startSection(SectionId::UserDefined, sectionStart, sectionSize, "user-defined"))
-                return false;
-            if (*sectionStart == NotStarted) {
-                cur_ = before;
-                return true;
-            }
-            uint32_t idSize;
-            if (!readVarU32(&idSize))
-                goto fail;
-            if (idSize > bytesRemain() || currentOffset() + idSize > *sectionStart + *sectionSize)
-                goto fail;
-            if (expectedId && (expectedIdSize != idSize || !!memcmp(cur_, expectedId, idSize))) {
-                finishUserDefinedSection(*sectionStart, *sectionSize);
-                continue;
-            }
-            cur_ += idSize;
-            return true;
-        }
-        MOZ_CRASH("unreachable");
-      fail:
-        return fail("failed to start user-defined section");
+        MOZ_ASSERT(name[NameSizeWith0 - 1] == '\0');
+        return startCustomSection(name, NameSizeWith0 - 1, env, sectionStart, sectionSize);
     }
-    template <size_t IdSizeWith0>
-    MOZ_MUST_USE bool startUserDefinedSection(const char (&id)[IdSizeWith0],
-                                              uint32_t* sectionStart,
-                                              uint32_t* sectionSize)
-    {
-        MOZ_ASSERT(id[IdSizeWith0 - 1] == '\0');
-        return startUserDefinedSection(id, IdSizeWith0 - 1, sectionStart, sectionSize);
-    }
-    void finishUserDefinedSection(uint32_t sectionStart, uint32_t sectionSize) {
-        MOZ_ASSERT(cur_ >= beg_);
-        MOZ_ASSERT(cur_ <= end_);
-        cur_ = (beg_ + sectionStart) + sectionSize;
-        MOZ_ASSERT(cur_ <= end_);
-        clearError();
-    }
-    MOZ_MUST_USE bool skipUserDefinedSection() {
-        uint32_t sectionStart, sectionSize;
-        if (!startUserDefinedSection(nullptr, 0, &sectionStart, &sectionSize))
-            return false;
-        if (sectionStart == NotStarted)
-            return fail("expected user-defined section");
-        finishUserDefinedSection(sectionStart, sectionSize);
-        return true;
-    }
+    void finishCustomSection(uint32_t sectionStart, uint32_t sectionSize);
+    MOZ_MUST_USE bool skipCustomSection(ModuleEnvironment* env);
+
 
     // The infallible "unchecked" decoding functions can be used when we are
     // sure that the bytes are well-formed (by construction or due to previous
     // validation).
 
     uint8_t uncheckedReadFixedU8() {
         return uncheckedRead<uint8_t>();
     }
@@ -618,114 +623,44 @@ class Decoder
     }
     void uncheckedReadFixedF32x4(F32x4* f32x4) {
         struct T { F32x4 v; };
         T t = uncheckedRead<T>();
         memcpy(f32x4, &t, sizeof(t));
     }
 };
 
-// Reusable macro encoding/decoding functions reused by both the two
-// encoders (AsmJS/WasmTextToBinary) and all the decoders
-// (WasmCompile/WasmIonCompile/WasmBaselineCompile/WasmBinaryToText).
-
-// Misc helpers.
+// The local entries are part of function bodies and thus serialized by both
+// wasm and asm.js and decoded as part of both validation and compilation.
 
 MOZ_MUST_USE bool
 EncodeLocalEntries(Encoder& d, const ValTypeVector& locals);
 
 MOZ_MUST_USE bool
 DecodeLocalEntries(Decoder& d, ModuleKind kind, ValTypeVector* locals);
 
-// ModuleEnvironment contains all the state necessary to validate, process or
-// render functions. It is created by decoding all the sections before the wasm
-// code section and then used immutably during. When compiling a module using a
-// ModuleGenerator, the ModuleEnvironment holds state shared between the
-// ModuleGenerator thread and background compile threads. All the threads
-// are given a read-only view of the ModuleEnvironment, thus preventing race
-// conditions.
-
-struct ModuleEnvironment
-{
-    ModuleKind                kind;
-    MemoryUsage               memoryUsage;
-    mozilla::Atomic<uint32_t> minMemoryLength;
-    Maybe<uint32_t>           maxMemoryLength;
-
-    SigWithIdVector           sigs;
-    SigWithIdPtrVector        funcSigs;
-    Uint32Vector              funcImportGlobalDataOffsets;
-    GlobalDescVector          globals;
-    TableDescVector           tables;
-    Uint32Vector              asmJSSigToTableIndex;
-    ImportVector              imports;
-    ExportVector              exports;
-    Maybe<uint32_t>           startFuncIndex;
-    ElemSegmentVector         elemSegments;
-
-    explicit ModuleEnvironment(ModuleKind kind = ModuleKind::Wasm)
-      : kind(kind),
-        memoryUsage(MemoryUsage::None),
-        minMemoryLength(0)
-    {}
-
-    size_t numTables() const {
-        return tables.length();
-    }
-    size_t numSigs() const {
-        return sigs.length();
-    }
-    size_t numFuncs() const {
-        // asm.js pre-reserves a bunch of function index space which is
-        // incrementally filled in during function-body validation. Thus, there
-        // are a few possible interpretations of numFuncs() (total index space
-        // size vs.  exact number of imports/definitions encountered so far) and
-        // to simplify things we simply only define this quantity for wasm.
-        MOZ_ASSERT(!isAsmJS());
-        return funcSigs.length();
-    }
-    size_t numFuncDefs() const {
-        // asm.js overallocates the length of funcSigs and in general does not
-        // know the number of function definitions until it's done compiling.
-        MOZ_ASSERT(!isAsmJS());
-        return funcSigs.length() - funcImportGlobalDataOffsets.length();
-    }
-    size_t numFuncImports() const {
-        MOZ_ASSERT(!isAsmJS());
-        return funcImportGlobalDataOffsets.length();
-    }
-    bool usesMemory() const {
-        return UsesMemory(memoryUsage);
-    }
-    bool isAsmJS() const {
-        return kind == ModuleKind::AsmJS;
-    }
-    bool funcIsImport(uint32_t funcIndex) const {
-        return funcIndex < funcImportGlobalDataOffsets.length();
-    }
-    uint32_t funcIndexToSigIndex(uint32_t funcIndex) const {
-        return funcSigs[funcIndex] - sigs.begin();
-    }
-};
-
-typedef UniquePtr<ModuleEnvironment> UniqueModuleEnvironment;
-
-// Section macros.
+// Calling DecodeModuleEnvironment decodes all sections up to the code section
+// and performs full validation of all those sections. The client must then
+// decode the code section itself, reusing ValidateFunctionBody if necessary,
+// and finally call DecodeModuleTail to decode all remaining sections after the
+// code section (again, performing full validation).
 
 MOZ_MUST_USE bool
 DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env);
 
 MOZ_MUST_USE bool
-DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* segments);
+ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d);
 
 MOZ_MUST_USE bool
-DecodeUnknownSections(Decoder& d);
+DecodeModuleTail(Decoder& d, ModuleEnvironment* env);
 
-MOZ_MUST_USE bool
-ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d);
+// Validate an entire module, returning true if the module was validated
+// successfully. If Validate returns false:
+//  - if *error is null, the caller should report out-of-memory
+//  - otherwise, there was a legitimate error described by *error
 
 MOZ_MUST_USE bool
 Validate(const ShareableBytes& bytecode, UniqueChars* error);
 
 }  // namespace wasm
 }  // namespace js
 
 #endif // namespace wasm_validate_h
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1557,18 +1557,18 @@ needs-focus == 568441.html 568441-ref.ht
 == 571347-2a.html 571347-2-ref.html
 == 571347-2b.html 571347-2-ref.html
 == 571347-2c.html 571347-2-ref.html
 == 571347-2d.html 571347-2-ref.html
 == 571347-3.html 571347-3-ref.html
 == 572598-1.html 572598-ref.html
 == 574898-1.html 574898-ref.html
 # 574907 is a windows-only issue, result on other platforms depends on details of font support
-random-if(!winWidget) fails-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)) == 574907-1.html 574907-1-ref.html # Bug 1258240
-random-if(!winWidget) fails-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)) == 574907-2.html 574907-2-ref.html # Bug 1258240
+random-if(!winWidget) random-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)) == 574907-1.html 574907-1-ref.html # Bug 1258240
+random-if(!winWidget) random-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)) == 574907-2.html 574907-2-ref.html # Bug 1258240
 # 574907-3 only worked under directwrite, and even there it now depends on the rendering mode; marking as random for now
 random-if(!winWidget) fails-if(winWidget&&!dwrite) random-if(winWidget&&dwrite) != 574907-3.html 574907-3-notref.html
 == 577838-1.html 577838-1-ref.html
 == 577838-2.html 577838-2-ref.html
 == 579323-1.html 579323-1-ref.html
 == 579349-1.html 579349-1-ref.html
 == 579655-1.html 579655-1-ref.html
 skip-if(!haveTestPlugin) fails-if(Android) == 579808-1.html 579808-1-ref.html
--- a/layout/reftests/transform-3d/reftest.list
+++ b/layout/reftests/transform-3d/reftest.list
@@ -18,18 +18,18 @@ fuzzy-if(gtkWidget||winWidget,8,376) fuz
 == preserve3d-2b.html preserve3d-2-ref.html
 == preserve3d-2c.html preserve3d-2-ref.html
 == preserve3d-2d.html preserve3d-2-ref.html
 == preserve3d-3a.html preserve3d-3-ref.html
 == preserve3d-4a.html about:blank
 fuzzy-if(gtkWidget,4,200) fuzzy-if(Android,4,300) fuzzy-if(winWidget&&!layersGPUAccelerated,2,100) fuzzy-if(skiaContent,16,100) == preserve3d-5a.html preserve3d-5-ref.html
 == preserve3d-6a.html preserve3d-6-ref.html
 == scale3d-z.html scalez-1-ref.html
-fuzzy-if(winWidget,102,580) fuzzy-if(d2d,143,681) fuzzy-if(OSX>=1008,224,924) == scale3d-all.html scale3d-1-ref.html # subpixel AA
-fuzzy-if(winWidget,102,580) fuzzy-if(d2d,143,681) fuzzy-if(OSX>=1008,224,924) == scale3d-all-separate.html scale3d-1-ref.html # subpixel AA
+fuzzy-if(winWidget,143,689) fuzzy-if(OSX>=1008,224,924) == scale3d-all.html scale3d-1-ref.html # subpixel AA
+fuzzy-if(winWidget,143,689) fuzzy-if(OSX>=1008,224,924) == scale3d-all-separate.html scale3d-1-ref.html # subpixel AA
 == scale3d-xz.html scale3d-1-ref.html
 == translatez-1a.html translatez-1-ref.html
 != translatez-1b.html translatez-1-ref.html
 == translate3d-1a.html translate3d-1-ref.html
 fuzzy-if(skiaContent,1,4) == matrix3d-1a.html matrix3d-1-ref.html
 == matrix3d-2a.html matrix3d-2-ref.html
 == rotate3d-1a.html rotatex-1-ref.html
 == rotate3d-2a.html rotatey-1-ref.html
--- a/layout/tools/reftest/reftest-preferences.js
+++ b/layout/tools/reftest/reftest-preferences.js
@@ -1,15 +1,16 @@
 // For mochitests, we're more interested in testing the behavior of in-
 // content XBL bindings, so we set this pref to true. In reftests, we're
 // more interested in testing the behavior of XBL as it works in chrome,
 // so we want this pref to be false.
 user_pref("dom.use_xbl_scopes_for_remote_xul", false);
 user_pref("gfx.color_management.mode", 2);
 user_pref("gfx.color_management.force_srgb", true);
+user_pref("gfx.logging.level", 1);
 user_pref("browser.dom.window.dump.enabled", true);
 user_pref("ui.caretBlinkTime", -1);
 user_pref("dom.send_after_paint_to_content", true);
 // no slow script dialogs
 user_pref("dom.max_script_run_time", 0);
 user_pref("dom.max_chrome_script_run_time", 0);
 user_pref("hangmonitor.timeout", 0);
 // Ensure autoplay is enabled for all platforms.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4635,35 +4635,29 @@ pref("layers.offmainthreadcomposition.as
 // Whether to log information about off main thread animations to stderr
 pref("layers.offmainthreadcomposition.log-animations", false);
 
 pref("layers.bufferrotation.enabled", true);
 
 pref("layers.componentalpha.enabled", true);
 pref("layers.draw-mask-debug", false);
 
-// Use the DT-backend implemented PushLayer
-pref("gfx.content.use-native-pushlayer", false);
-
 pref("gfx.content.always-paint", false);
 
 #ifdef ANDROID
 pref("gfx.apitrace.enabled",false);
 #endif
 
 #ifdef MOZ_X11
-pref("gfx.content.use-native-pushlayer", true);
 #ifdef MOZ_WIDGET_GTK
 pref("gfx.xrender.enabled",false);
 #endif
 #endif
 
 #ifdef XP_WIN
-pref("gfx.content.use-native-pushlayer", true);
-
 // Whether to disable the automatic detection and use of direct2d.
 pref("gfx.direct2d.disabled", false);
 
 // Whether to attempt to enable Direct2D regardless of automatic detection or
 // blacklisting
 pref("gfx.direct2d.force-enabled", false);
 
 pref("layers.prefer-opengl", false);
@@ -5544,13 +5538,19 @@ pref ("security.mixed_content.hsts_primi
 
 // Disable Storage api in release builds.
 #ifdef NIGHTLY_BUILD
 pref("dom.storageManager.enabled", true);
 #else
 pref("dom.storageManager.enabled", false);
 #endif
 
+// When a user cancels this number of authentication dialogs coming from
+// a single web page in a row, all following authentication dialogs will
+// be blocked (automatically canceled) for that page. The counter resets
+// when the page is reloaded. To turn this feature off, just set the limit to 0.
+pref("prompts.authentication_dialog_abuse_limit", 3);
+
 // Enable the Storage management in about:preferences and persistent-storage permission request
 // To enable the DOM implementation, turn on "dom.storageManager.enabled"
 pref("browser.storageManager.enabled", false);
 
 pref("dom.IntersectionObserver.enabled", true);
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -125,16 +125,17 @@ struct HttpChannelOpenArgs
   nsCString                   requestContextID;
   OptionalCorsPreflightArgs   preflightArgs;
   uint32_t                    initialRwin;
   bool                        blockAuthPrompt;
   bool                        suspendAfterSynthesizeResponse;
   bool                        allowStaleCacheContent;
   nsCString                   contentTypeHint;
   nsCString                   channelId;
+  uint64_t                    contentWindowId;
   nsCString                   preferredAlternativeType;
 };
 
 struct HttpChannelConnectArgs
 {
   uint32_t registrarId;
   bool shouldIntercept;
 };
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -50,18 +50,20 @@
 #include "nsISSLSocketControl.h"
 #include "mozilla/Telemetry.h"
 #include "nsIURL.h"
 #include "nsIConsoleService.h"
 #include "mozilla/BinarySearch.h"
 #include "nsIHttpHeaderVisitor.h"
 #include "nsIXULRuntime.h"
 #include "nsICacheInfoChannel.h"
+#include "nsIDOMWindowUtils.h"
 
 #include <algorithm>
+#include "HttpBaseChannel.h"
 
 namespace mozilla {
 namespace net {
 
 HttpBaseChannel::HttpBaseChannel()
   : mStartPos(UINT64_MAX)
   , mStatus(NS_OK)
   , mLoadFlags(LOAD_NORMAL)
@@ -108,16 +110,17 @@ HttpBaseChannel::HttpBaseChannel()
   , mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW)
   , mFetchCacheMode(nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT)
   , mOnStartRequestCalled(false)
   , mOnStopRequestCalled(false)
   , mAfterOnStartRequestBegun(false)
   , mTransferSize(0)
   , mDecodedBodySize(0)
   , mEncodedBodySize(0)
+  , mContentWindowId(0)
   , mRequireCORSPreflight(false)
   , mReportCollector(new ConsoleReportCollector())
   , mForceMainDocumentChannel(false)
 {
   LOG(("Creating HttpBaseChannel @%x\n", this));
 
   // Subfields of unions cannot be targeted in an initializer list.
 #ifdef MOZ_VALGRIND
@@ -1191,16 +1194,40 @@ HttpBaseChannel::SetChannelId(const nsAC
   if (newId.Parse(idStr.get())) {
     mChannelId = newId;
     return NS_OK;
   }
 
   return NS_ERROR_FAILURE;
 }
 
+NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+  if (!mContentWindowId) {
+    nsCOMPtr<nsILoadContext> loadContext;
+    GetCallback(loadContext);
+    if (loadContext) {
+      nsCOMPtr<mozIDOMWindowProxy> topWindow;
+      loadContext->GetTopWindow(getter_AddRefs(topWindow));
+      nsCOMPtr<nsIDOMWindowUtils> windowUtils = do_GetInterface(topWindow);
+      if (windowUtils) {
+        windowUtils->GetCurrentInnerWindowID(&mContentWindowId);
+      }
+    }
+  }
+  *aWindowId = mContentWindowId;
+  return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+  mContentWindowId = aWindowId;
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 HttpBaseChannel::GetTransferSize(uint64_t *aTransferSize)
 {
   *aTransferSize = mTransferSize;
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -183,16 +183,18 @@ public:
   NS_IMETHOD GetDecodedBodySize(uint64_t *aDecodedBodySize) override;
   NS_IMETHOD GetEncodedBodySize(uint64_t *aEncodedBodySize) override;
   NS_IMETHOD SetRequestContextID(const nsID aRCID) override;
   NS_IMETHOD GetIsMainDocumentChannel(bool* aValue) override;
   NS_IMETHOD SetIsMainDocumentChannel(bool aValue) override;
   NS_IMETHOD GetProtocolVersion(nsACString & aProtocolVersion) override;
   NS_IMETHOD GetChannelId(nsACString& aChannelId) override;
   NS_IMETHOD SetChannelId(const nsACString& aChannelId) override;
+  NS_IMETHOD GetTopLevelContentWindowId(uint64_t *aContentWindowId) override;
+  NS_IMETHOD SetTopLevelContentWindowId(uint64_t aContentWindowId) override;
 
   // nsIHttpChannelInternal
   NS_IMETHOD GetDocumentURI(nsIURI **aDocumentURI) override;
   NS_IMETHOD SetDocumentURI(nsIURI *aDocumentURI) override;
   NS_IMETHOD GetRequestVersion(uint32_t *major, uint32_t *minor) override;
   NS_IMETHOD GetResponseVersion(uint32_t *major, uint32_t *minor) override;
   NS_IMETHOD SetCookie(const char *aCookieHeader) override;
   NS_IMETHOD GetThirdPartyFlags(uint32_t *aForce) override;
@@ -545,16 +547,20 @@ protected:
   uint64_t mEncodedBodySize;
 
   // The network interface id that's associated with this channel.
   nsCString mNetworkInterfaceId;
 
   nsID mRequestContextID;
   bool EnsureRequestContextID();
 
+  // ID of the top-level document's inner window this channel is being
+  // originated from.
+  uint64_t mContentWindowId;
+
   bool                              mRequireCORSPreflight;
   nsTArray<nsCString>               mUnsafeHeaders;
 
   nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
 
   // Holds the name of the preferred alt-data type.
   nsCString mPreferredCachedAltDataType;
   // Holds the name of the alternative data type the channel returned.
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -33,16 +33,18 @@
 #include "mozilla/net/DNS.h"
 #include "SerializedLoadContext.h"
 #include "nsInputStreamPump.h"
 #include "InterceptedChannel.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsContentSecurityManager.h"
 #include "nsIDeprecationWarner.h"
 #include "nsICompressConvStats.h"
+#include "nsIDocument.h"
+#include "nsIDOMWindowUtils.h"
 #include "nsStreamUtils.h"
 
 #ifdef OS_POSIX
 #include "chrome/common/file_descriptor_set_posix.h"
 #endif
 
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
@@ -2039,16 +2041,28 @@ HttpChannelChild::ContinueAsyncOpen()
   GetCallback(iTabChild);
   if (iTabChild) {
     tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
   }
   if (MissingRequiredTabChild(tabChild, "http")) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
+  // This id identifies the inner window's top-level document,
+  // which changes on every new load or navigation.
+  uint64_t contentWindowId = 0;
+  if (tabChild) {
+    MOZ_ASSERT(tabChild->WebNavigation());
+    nsCOMPtr<nsIDocument> document = tabChild->GetDocument();
+    if (document) {
+      contentWindowId = document->InnerWindowID();
+    }
+  }
+  SetTopLevelContentWindowId(contentWindowId);
+
   HttpChannelOpenArgs openArgs;
   // No access to HttpChannelOpenArgs members, but they each have a
   // function with the struct name that returns a ref.
   SerializeURI(mURI, openArgs.uri());
   SerializeURI(mOriginalURI, openArgs.original());
   SerializeURI(mDocumentURI, openArgs.doc());
   SerializeURI(mReferrer, openArgs.referrer());
   openArgs.referrerPolicy() = mReferrerPolicy;
@@ -2133,16 +2147,18 @@ HttpChannelChild::ContinueAsyncOpen()
   char rcid[NSID_LENGTH];
   mRequestContextID.ToProvidedString(rcid);
   openArgs.requestContextID().AssignASCII(rcid);
 
   char chid[NSID_LENGTH];
   mChannelId.ToProvidedString(chid);
   openArgs.channelId().AssignASCII(chid);
 
+  openArgs.contentWindowId() = contentWindowId;
+
   if (tabChild && !tabChild->IPCOpen()) {
     return NS_ERROR_FAILURE;
   }
 
   ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
   if (cc->IsShuttingDown()) {
     return NS_ERROR_FAILURE;
   }
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -125,17 +125,17 @@ HttpChannelParent::Init(const HttpChanne
                        a.entityID(), a.chooseApplicationCache(),
                        a.appCacheClientID(), a.allowSpdy(), a.allowAltSvc(), a.beConservative(),
                        a.loadInfo(), a.synthesizedResponseHead(),
                        a.synthesizedSecurityInfoSerialization(),
                        a.cacheKey(), a.requestContextID(), a.preflightArgs(),
                        a.initialRwin(), a.blockAuthPrompt(),
                        a.suspendAfterSynthesizeResponse(),
                        a.allowStaleCacheContent(), a.contentTypeHint(),
-                       a.channelId(), a.preferredAlternativeType());
+                       a.channelId(), a.contentWindowId(), a.preferredAlternativeType());
   }
   case HttpChannelCreationArgs::THttpChannelConnectArgs:
   {
     const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs();
     return ConnectChannel(cArgs.registrarId(), cArgs.shouldIntercept());
   }
   default:
     NS_NOTREACHED("unknown open type");
@@ -323,16 +323,17 @@ HttpChannelParent::DoAsyncOpen(  const U
                                  const nsCString&           aRequestContextID,
                                  const OptionalCorsPreflightArgs& aCorsPreflightArgs,
                                  const uint32_t&            aInitialRwin,
                                  const bool&                aBlockAuthPrompt,
                                  const bool&                aSuspendAfterSynthesizeResponse,
                                  const bool&                aAllowStaleCacheContent,
                                  const nsCString&           aContentTypeHint,
                                  const nsCString&           aChannelId,
+                                 const uint64_t&            aContentWindowId,
                                  const nsCString&           aPreferredAlternativeType)
 {
   nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
   if (!uri) {
     // URIParams does MOZ_ASSERT if null, but we need to protect opt builds from
     // null deref here.
     return false;
   }
@@ -372,16 +373,17 @@ HttpChannelParent::DoAsyncOpen(  const U
     return SendFailedAsyncOpen(rv);
 
   // This cast is safe since this is AsyncOpen specific to http.  channel
   // is ensured to be nsHttpChannel.
   mChannel = static_cast<nsHttpChannel *>(channel.get());
 
   // Set the channelId allocated in child to the parent instance
   mChannel->SetChannelId(aChannelId);
+  mChannel->SetTopLevelContentWindowId(aContentWindowId);
 
   mChannel->SetWarningReporter(this);
   mChannel->SetTimingEnabled(true);
   if (mPBOverride != kPBOverride_Unset) {
     mChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
   }
 
   if (doResumeAt)
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -137,16 +137,17 @@ protected:
                    const nsCString&           aRequestContextID,
                    const OptionalCorsPreflightArgs& aCorsPreflightArgs,
                    const uint32_t&            aInitialRwin,
                    const bool&                aBlockAuthPrompt,
                    const bool&                aSuspendAfterSynthesizeResponse,
                    const bool&                aAllowStaleCacheContent,
                    const nsCString&           aContentTypeHint,
                    const nsCString&           aChannelId,
+                   const uint64_t&            aContentWindowId,
                    const nsCString&           aPreferredAlternativeType);
 
   virtual mozilla::ipc::IPCResult RecvSetPriority(const uint16_t& priority) override;
   virtual mozilla::ipc::IPCResult RecvSetClassOfService(const uint32_t& cos) override;
   virtual mozilla::ipc::IPCResult RecvSetCacheTokenCachedCharset(const nsCString& charset) override;
   virtual mozilla::ipc::IPCResult RecvSuspend() override;
   virtual mozilla::ipc::IPCResult RecvResume() override;
   virtual mozilla::ipc::IPCResult RecvCancel(const nsresult& status) override;
--- a/netwerk/protocol/http/NullHttpChannel.cpp
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -65,16 +65,28 @@ NullHttpChannel::GetChannelId(nsACString
 
 NS_IMETHODIMP
 NullHttpChannel::SetChannelId(const nsACString& aChannelId)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
+NullHttpChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
 NullHttpChannel::GetTransferSize(uint64_t *aTransferSize)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 NullHttpChannel::GetDecodedBodySize(uint64_t *aDecodedBodySize)
 {
--- a/netwerk/protocol/http/nsIHttpChannel.idl
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -458,9 +458,15 @@ interface nsIHttpChannel : nsIChannel
     [noscript] attribute nsID requestContextID;
 
     /**
      * Unique ID of the channel, shared between parent and child. Needed if
      * the channel activity needs to be monitored across process boundaries,
      * like in devtools net monitor. See bug 1274556.
      */
     attribute ACString channelId;
+
+    /**
+     * ID of the top-level document's inner window.  Identifies the content
+     * this channels is being load in.
+     */
+    attribute uint64_t topLevelContentWindowId;
 };
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -721,16 +721,30 @@ nsViewSourceChannel::GetChannelId(nsACSt
 NS_IMETHODIMP
 nsViewSourceChannel::SetChannelId(const nsACString& aChannelId)
 {
   return !mHttpChannel ? NS_ERROR_NULL_POINTER :
       mHttpChannel->SetChannelId(aChannelId);
 }
 
 NS_IMETHODIMP
+nsViewSourceChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+  return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+      mHttpChannel->GetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+  return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+      mHttpChannel->SetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
 nsViewSourceChannel::GetRequestMethod(nsACString & aRequestMethod)
 {
     return !mHttpChannel ? NS_ERROR_NULL_POINTER :
         mHttpChannel->GetRequestMethod(aRequestMethod);
 }
 
 NS_IMETHODIMP
 nsViewSourceChannel::SetRequestMethod(const nsACString & aRequestMethod)
--- a/python/mozbuild/mozbuild/mozinfo.py
+++ b/python/mozbuild/mozbuild/mozinfo.py
@@ -90,16 +90,17 @@ def build_dict(config, env=os.environ):
     d['asan'] = substs.get('MOZ_ASAN') == '1'
     d['tsan'] = substs.get('MOZ_TSAN') == '1'
     d['telemetry'] = substs.get('MOZ_TELEMETRY_REPORTING') == '1'
     d['tests_enabled'] = substs.get('ENABLE_TESTS') == "1"
     d['bin_suffix'] = substs.get('BIN_SUFFIX', '')
     d['addon_signing'] = substs.get('MOZ_ADDON_SIGNING') == '1'
     d['require_signing'] = substs.get('MOZ_REQUIRE_SIGNING') == '1'
     d['official'] = bool(substs.get('MOZILLA_OFFICIAL'))
+    d['updater'] = substs.get('MOZ_UPDATER') == '1'
 
     def guess_platform():
         if d['buildapp'] in ('browser', 'mulet'):
             p = d['os']
             if p == 'mac':
                 p = 'macosx64'
             elif d['bits'] == 64:
                 p = '{}64'.format(p)
--- a/taskcluster/taskgraph/transforms/tests/make_task_description.py
+++ b/taskcluster/taskgraph/transforms/tests/make_task_description.py
@@ -341,22 +341,17 @@ def generic_worker_setup(config, test, t
         ['generic-worker:os-group:{}'.format(group) for group in test['os-groups']])
 
     worker = taskdesc['worker'] = {}
     worker['os-groups'] = test['os-groups']
     worker['implementation'] = test['worker-implementation']
     worker['max-run-time'] = test['max-run-time']
     worker['artifacts'] = artifacts
 
-    worker['env'] = {
-        'APPDATA': '%cd%\\AppData\\Roaming',
-        'LOCALAPPDATA': '%cd%\\AppData\\Local',
-        'TEMP': '%cd%\\AppData\\Local\\Temp',
-        'TMP': '%cd%\\AppData\\Local\\Temp',
-    }
+    worker['env'] = {}
 
     # assemble the command line
     mh_command = [
         'c:\\mozilla-build\\python\\python.exe',
         '-u',
         'mozharness\\scripts\\' + normpath(mozharness['script'])
     ]
     for mh_config in mozharness['config']:
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -919,38 +919,46 @@ this.BrowserTestUtils = {
         if ('nsICrashReporter' in Ci) {
           dumpID = subject.getPropertyAsAString('dumpID');
           if (!dumpID) {
             return reject("dumpID was not present despite crash reporting " +
                           "being enabled");
           }
         }
 
+        let removalPromise = Promise.resolve();
+
         if (dumpID) {
-          let minidumpDirectory = getMinidumpDirectory();
-          let extrafile = minidumpDirectory.clone();
-          extrafile.append(dumpID + '.extra');
-          if (extrafile.exists()) {
-            dump(`\nNo .extra file for dumpID: ${dumpID}\n`);
-            if (AppConstants.MOZ_CRASHREPORTER) {
-              extra = KeyValueParser.parseKeyValuePairsFromFile(extrafile);
-            } else {
-              dump('\nCrashReporter not enabled - will not return any extra data\n');
+          removalPromise = Services.crashmanager.ensureCrashIsPresent(dumpID)
+                                                .then(() => {
+            let minidumpDirectory = getMinidumpDirectory();
+            let extrafile = minidumpDirectory.clone();
+            extrafile.append(dumpID + '.extra');
+            if (extrafile.exists()) {
+              dump(`\nNo .extra file for dumpID: ${dumpID}\n`);
+              if (AppConstants.MOZ_CRASHREPORTER) {
+                extra = KeyValueParser.parseKeyValuePairsFromFile(extrafile);
+              } else {
+                dump('\nCrashReporter not enabled - will not return any extra data\n');
+              }
             }
-          }
 
-          removeFile(minidumpDirectory, dumpID + '.dmp');
-          removeFile(minidumpDirectory, dumpID + '.extra');
+            removeFile(minidumpDirectory, dumpID + '.dmp');
+            removeFile(minidumpDirectory, dumpID + '.extra');
+          });
         }
 
-        Services.obs.removeObserver(observer, 'ipc:content-shutdown');
-        dump("\nCrash cleaned up\n");
-        // There might be other ipc:content-shutdown handlers that need to run before
-        // we want to continue, so we'll resolve on the next tick of the event loop.
-        TestUtils.executeSoon(() => resolve());
+        removalPromise.then(() => {
+          Services.obs.removeObserver(observer, 'ipc:content-shutdown');
+          dump("\nCrash cleaned up\n");
+          // There might be other ipc:content-shutdown handlers that need to
+          // run before we want to continue, so we'll resolve on the next tick
+          // of the event loop.
+          TestUtils.executeSoon(() => resolve());
+        });
       };
 
       Services.obs.addObserver(observer, 'ipc:content-shutdown', false);
     });
 
     expectedPromises.push(crashCleanupPromise);
 
     if (shouldShowTabCrashPage) {
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -27,16 +27,17 @@ user_pref("accessibility.typeaheadfind.a
 user_pref("findbar.highlightAll", false);
 user_pref("findbar.modalHighlight", false);
 user_pref("javascript.options.showInConsole", true);
 user_pref("devtools.browsertoolbox.panel", "jsdebugger");
 user_pref("devtools.debugger.remote-port", 6023);
 user_pref("devtools.devedition.promo.enabled", false);
 user_pref("browser.EULA.override", true);
 user_pref("gfx.color_management.force_srgb", true);
+user_pref("gfx.logging.level", 1);
 user_pref("network.manage-offline-status", false);
 // Disable speculative connections so they aren't reported as leaking when they're hanging around.
 user_pref("network.http.speculative-parallel-limit", 0);
 user_pref("dom.min_background_timeout_value", 1000);
 user_pref("test.mousescroll", true);
 user_pref("security.default_personal_cert", "Select Automatically"); // Need to client auth test be w/o any dialogs
 user_pref("network.http.prompt-temp-redirect", false);
 user_pref("media.preload.default", 2); // default = metadata
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -34,16 +34,46 @@ const MILLISECONDS_IN_DAY = 24 * 60 * 60
 
 // Converts Date to days since UNIX epoch.
 // This was copied from /services/metrics.storage.jsm. The implementation
 // does not account for leap seconds.
 function dateToDays(date) {
   return Math.floor(date.getTime() / MILLISECONDS_IN_DAY);
 }
 
+/**
+ * Parse the string stored in the specified field as JSON and then remove the
+ * field from the object. The string might also be returned without parsing.
+ *
+ * @param obj {Object} The object holding the field
+ * @param field {String} The name of the field to be parsed and removed
+ * @param [parseAsJson=true] {Boolean} If true parse the field's contents as if
+ *        it were JSON code, otherwise return the rew string.
+ *
+ * @returns {Object|String} the parsed object or the raw string
+ */
+function parseAndRemoveField(obj, field, parseAsJson = true) {
+  let value = null;
+
+  if (field in obj) {
+    if (!parseAsJson) {
+      value = obj[field];
+    } else {
+      try {
+        value = JSON.parse(obj[field]);
+      } catch (e) {
+        Cu.reportError(e);
+      }
+    }
+
+    delete obj[field];
+  }
+
+  return value;
+}
 
 /**
  * A gateway to crash-related data.
  *
  * This type is generic and can be instantiated any number of times.
  * However, most applications will typically only have one instance
  * instantiated and that instance will point to profile and user appdata
  * directories.
@@ -109,16 +139,19 @@ this.CrashManager = function(options) {
 
   // Promise for in-progress aggregation operation. We store it on the
   // object so it can be returned for in-progress operations.
   this._aggregatePromise = null;
 
   // Map of crash ID / promise tuples used to track adding new crashes.
   this._crashPromises = new Map();
 
+  // Promise for the crash ping used only for testing.
+  this._pingPromise = null;
+
   // The CrashStore currently attached to this object.
   this._store = null;
 
   // A Task to retrieve the store. This is needed to avoid races when
   // _getStore() is called multiple times in a short interval.
   this._getStoreTask = null;
 
   // The timer controlling the expiration of the CrashStore instance.
@@ -169,16 +202,51 @@ this.CrashManager.prototype = Object.fre
   // The following are return codes for individual event file processing.
   // File processed OK.
   EVENT_FILE_SUCCESS: "ok",
   // The event appears to be malformed.
   EVENT_FILE_ERROR_MALFORMED: "malformed",
   // The type of event is unknown.
   EVENT_FILE_ERROR_UNKNOWN_EVENT: "unknown-event",
 
+  // A whitelist of crash annotations which do not contain sensitive data
+  // and are saved in the crash record and sent with Firefox Health Report.
+  ANNOTATION_WHITELIST: [
+    "AsyncShutdownTimeout",
+    "BuildID",
+    "ProductID",
+    "ProductName",
+    "ReleaseChannel",
+    "SecondsSinceLastCrash",
+    "ShutdownProgress",
+    "StartupCrash",
+    "TelemetryEnvironment",
+    "Version",
+    // The following entries are not normal annotations that can be found in
+    // the .extra file but are included in the crash record/FHR:
+    "AvailablePageFile",
+    "AvailablePhysicalMemory",
+    "AvailableVirtualMemory",
+    "BlockedDllList",
+    "BlocklistInitFailed",
+    "ContainsMemoryReport",
+    "CrashTime",
+    "EventLoopNestingLevel",
+    "IsGarbageCollecting",
+    "MozCrashReason",
+    "OOMAllocationSize",
+    "SystemMemoryUsePercentage",
+    "TextureUsage",
+    "TotalPageFile",
+    "TotalPhysicalMemory",
+    "TotalVirtualMemory",
+    "UptimeTS",
+    "User32BeforeBlocklist",
+  ],
+
   /**
    * Obtain a list of all dumps pending upload.
    *
    * The returned value is a promise that resolves to an array of objects
    * on success. Each element in the array has the following properties:
    *
    *   id (string)
    *      The ID of the crash (a UUID).
@@ -379,17 +447,22 @@ this.CrashManager.prototype = Object.fre
       }
 
       let deferred = this._crashPromises.get(id);
 
       if (deferred) {
         this._crashPromises.delete(id);
         deferred.resolve();
       }
-    }.bind(this));
+
+      // Send a telemetry ping for each content process crash
+      if (processType === this.PROCESS_TYPE_CONTENT) {
+        this._sendCrashPing(id, processType, date, metadata);
+      }
+   }.bind(this));
 
     return promise;
   },
 
   /**
    * Returns a promise that is resolved only the crash with the specified id
    * has been fully recorded.
    *
@@ -539,16 +612,61 @@ this.CrashManager.prototype = Object.fre
       }
       let date = new Date(time * 1000);
       let payload = data.substring(start);
 
       return this._handleEventFilePayload(store, entry, type, date, payload);
     }.bind(this));
   },
 
+  _filterAnnotations: function(annotations) {
+    let filteredAnnotations = {};
+
+    for (let line in annotations) {
+      if (this.ANNOTATION_WHITELIST.includes(line)) {
+        filteredAnnotations[line] = annotations[line];
+      }
+    }
+
+    return filteredAnnotations;
+  },
+
+  _sendCrashPing: function(crashId, type, date, metadata = {}) {
+    // If we have a saved environment, use it. Otherwise report
+    // the current environment.
+    let reportMeta = Cu.cloneInto(metadata, myScope);
+    let crashEnvironment = parseAndRemoveField(reportMeta,
+                                               "TelemetryEnvironment");
+    let sessionId = parseAndRemoveField(reportMeta, "TelemetrySessionId",
+                                        /* parseAsJson */ false);
+    let stackTraces = parseAndRemoveField(reportMeta, "StackTraces");
+
+    // Filter the remaining annotations to remove privacy-sensitive ones
+    reportMeta = this._filterAnnotations(reportMeta);
+
+    this._pingPromise = TelemetryController.submitExternalPing("crash",
+      {
+        version: 1,
+        crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
+        sessionId: sessionId,
+        crashId: crashId,
+        processType: type,
+        stackTraces: stackTraces,
+        metadata: reportMeta,
+        hasCrashEnvironment: (crashEnvironment !== null),
+      },
+      {
+        retentionDays: 180,
+        addClientId: true,
+        addEnvironment: true,
+        overrideEnvironment: crashEnvironment,
+      }
+    );
+  },
+
   _handleEventFilePayload: function(store, entry, type, date, payload) {
       // The payload types and formats are documented in docs/crash-events.rst.
       // Do not change the format of an existing type. Instead, invent a new
       // type.
       // DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
       let lines = payload.split("\n");
 
       switch (type) {
@@ -560,58 +678,17 @@ this.CrashManager.prototype = Object.fre
           }
           // fall-through
         case "crash.main.2":
           let crashID = lines[0];
           let metadata = parseKeyValuePairsFromLines(lines.slice(1));
           store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
                          crashID, date, metadata);
 
-          // If we have a saved environment, use it. Otherwise report
-          // the current environment.
-          let crashEnvironment = null;
-          let sessionId = null;
-          let stackTraces = null;
-          let reportMeta = Cu.cloneInto(metadata, myScope);
-          if ('TelemetryEnvironment' in reportMeta) {
-            try {
-              crashEnvironment = JSON.parse(reportMeta.TelemetryEnvironment);
-            } catch (e) {
-              Cu.reportError(e);
-            }
-            delete reportMeta.TelemetryEnvironment;
-          }
-          if ('TelemetrySessionId' in reportMeta) {
-            sessionId = reportMeta.TelemetrySessionId;
-            delete reportMeta.TelemetrySessionId;
-          }
-          if ('StackTraces' in reportMeta) {
-            try {
-              stackTraces = JSON.parse(reportMeta.StackTraces);
-            } catch (e) {
-              Cu.reportError(e);
-            }
-            delete reportMeta.StackTraces;
-          }
-          TelemetryController.submitExternalPing("crash",
-            {
-              version: 1,
-              crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
-              sessionId: sessionId,
-              crashId: entry.id,
-              stackTraces: stackTraces,
-              metadata: reportMeta,
-              hasCrashEnvironment: (crashEnvironment !== null),
-            },
-            {
-              retentionDays: 180,
-              addClientId: true,
-              addEnvironment: true,
-              overrideEnvironment: crashEnvironment,
-            });
+          this._sendCrashPing(crashID, this.PROCESS_TYPE_MAIN, date, metadata);
           break;
 
         case "crash.submission.1":
           if (lines.length == 3) {
             let [crashID, result, remoteID] = lines;
             store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
                            crashID, date);
 
--- a/toolkit/components/crashes/CrashService.js
+++ b/toolkit/components/crashes/CrashService.js
@@ -1,20 +1,54 @@
 /* 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/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
+Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
+Cu.import("resource://gre/modules/KeyValueParser.jsm");
+Cu.import("resource://gre/modules/osfile.jsm", this);
+Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 
 /**
+ * Process the .extra file associated with the crash id and return the
+ * annotations it contains in an object.
+ *
+ * @param crashID {string} Crash ID. Likely a UUID.
+ *
+ * @return {Promise} A promise that resolves to an object holding the crash
+ *         annotations, this object may be empty if no annotations were found.
+ */
+function processExtraFile(id) {
+  let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
+             .getService(Components.interfaces.nsICrashReporter);
+  let extraPath = OS.Path.join(cr.minidumpPath.path, id + ".extra");
+
+  return Task.spawn(function* () {
+    try {
+      let decoder = new TextDecoder();
+      let extraFile = yield OS.File.read(extraPath);
+      let extraData = decoder.decode(extraFile);
+
+      return parseKeyValuePairs(extraData);
+    } catch (e) {
+      Cu.reportError(e);
+    }
+
+    return {};
+  });
+}
+
+/**
  * This component makes crash data available throughout the application.
  *
  * It is a service because some background activity will eventually occur.
  */
 this.CrashService = function() {};
 
 CrashService.prototype = Object.freeze({
   classID: Components.ID("{92668367-1b17-4190-86b2-1061b2179744}"),
@@ -50,17 +84,23 @@ CrashService.prototype = Object.freeze({
       break;
     case Ci.nsICrashService.CRASH_TYPE_HANG:
       crashType = Services.crashmanager.CRASH_TYPE_HANG;
       break;
     default:
       throw new Error("Unrecognized CRASH_TYPE: " + crashType);
     }
 
-    Services.crashmanager.addCrash(processType, crashType, id, new Date());
+    AsyncShutdown.profileBeforeChange.addBlocker(
+      "CrashService waiting for content crash ping to be sent",
+      processExtraFile(id).then(metadata => {
+        return Services.crashmanager.addCrash(processType, crashType, id,
+                                              new Date(), metadata)
+      })
+    );
   },
 
   observe: function(subject, topic, data) {
     switch (topic) {
       case "profile-after-change":
         // Side-effect is the singleton is instantiated.
         Services.crashmanager;
         break;
--- a/toolkit/components/crashes/docs/index.rst
+++ b/toolkit/components/crashes/docs/index.rst
@@ -10,15 +10,21 @@ data within the Gecko application.
 From JavaScript, the service can be accessed via::
 
    Cu.import("resource://gre/modules/Services.jsm");
    let crashManager = Services.crashmanager;
 
 That will give you an instance of ``CrashManager`` from ``CrashManager.jsm``.
 From there, you can access and manipulate crash data.
 
+The crash manager stores statistical information about crashes as well as
+detailed information for both browser and content crashes. The crash manager
+automatically detects new browser crashes at startup by scanning for
+:ref:`crash-events`. Content process crash information on the other hand is
+provided externally.
+
 Other Documents
 ===============
 
 .. toctree::
    :maxdepth: 1
 
    crash-events
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
@@ -204,82 +204,100 @@ add_task(function* test_schedule_mainten
   yield m.createEventsFile("2", "crash.main.2", oldDate, "id2");
 
   yield m.scheduleMaintenance(25);
   let crashes = yield m.getCrashes();
   Assert.equal(crashes.length, 1);
   Assert.equal(crashes[0].id, "id1");
 });
 
+const crashId = "3cb67eba-0dc7-6f78-6a569a0e-172287ec";
+const productName = "Firefox";
+const productId = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+const stackTraces = "{\"status\":\"OK\"}";
+
 add_task(function* test_main_crash_event_file() {
   let ac = new TelemetryArchiveTesting.Checker();
   yield ac.promiseInit();
   let theEnvironment = TelemetryEnvironment.currentEnvironment;
-  let sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
-  let stackTraces = { status: "OK" };
+  const sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
 
   // To test proper escaping, add data to the environment with an embedded
   // double-quote
   theEnvironment.testValue = "MyValue\"";
 
   let m = yield getManager();
-  const fileContent = "id1\nk1=v1\nk2=v2\n" +
+  const fileContent = crashId + "\n" +
+    "ProductName=" + productName + "\n" +
+    "ProductID=" + productId + "\n" +
     "TelemetryEnvironment=" + JSON.stringify(theEnvironment) + "\n" +
     "TelemetrySessionId=" + sessionId + "\n" +
-    "StackTraces=" + JSON.stringify(stackTraces) + "\n";
+    "StackTraces=" + stackTraces + "\n" +
+    "ThisShouldNot=end-up-in-the-ping\n";
 
-  yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, fileContent);
+  yield m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
   let count = yield m.aggregateEventsFiles();
   Assert.equal(count, 1);
 
   let crashes = yield m.getCrashes();
   Assert.equal(crashes.length, 1);
-  Assert.equal(crashes[0].id, "id1");
+  Assert.equal(crashes[0].id, crashId);
   Assert.equal(crashes[0].type, "main-crash");
-  Assert.equal(crashes[0].metadata.k1, "v1");
-  Assert.equal(crashes[0].metadata.k2, "v2");
+  Assert.equal(crashes[0].metadata.ProductName, productName);
+  Assert.equal(crashes[0].metadata.ProductID, productId);
   Assert.ok(crashes[0].metadata.TelemetryEnvironment);
-  Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 5);
+  Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 6);
   Assert.equal(crashes[0].metadata.TelemetrySessionId, sessionId);
   Assert.ok(crashes[0].metadata.StackTraces);
   Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
 
   let found = yield ac.promiseFindPing("crash", [
     [["payload", "hasCrashEnvironment"], true],
-    [["payload", "metadata", "k1"], "v1"],
-    [["payload", "crashId"], "1"],
+    [["payload", "metadata", "ProductName"], productName],
+    [["payload", "metadata", "ProductID"], productId],
+    [["payload", "crashId"], crashId],
     [["payload", "stackTraces", "status"], "OK"],
     [["payload", "sessionId"], sessionId],
   ]);
   Assert.ok(found, "Telemetry ping submitted for found crash");
-  Assert.deepEqual(found.environment, theEnvironment, "The saved environment should be present");
+  Assert.deepEqual(found.environment, theEnvironment,
+                   "The saved environment should be present");
+  Assert.equal(found.payload.metadata.ThisShouldNot, undefined,
+               "Non-whitelisted fields should be filtered out");
 
   count = yield m.aggregateEventsFiles();
   Assert.equal(count, 0);
 });
 
 add_task(function* test_main_crash_event_file_noenv() {
   let ac = new TelemetryArchiveTesting.Checker();
   yield ac.promiseInit();
+  const fileContent = crashId + "\n" +
+    "ProductName=" + productName + "\n" +
+    "ProductID=" + productId + "\n";
 
   let m = yield getManager();
-  yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "id1\nk1=v3\nk2=v2");
+  yield m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
   let count = yield m.aggregateEventsFiles();
   Assert.equal(count, 1);
 
   let crashes = yield m.getCrashes();
   Assert.equal(crashes.length, 1);
-  Assert.equal(crashes[0].id, "id1");
+  Assert.equal(crashes[0].id, crashId);
   Assert.equal(crashes[0].type, "main-crash");
-  Assert.deepEqual(crashes[0].metadata, { k1: "v3", k2: "v2"});
+  Assert.deepEqual(crashes[0].metadata, {
+    ProductName: productName,
+    ProductID: productId
+  });
   Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
 
   let found = yield ac.promiseFindPing("crash", [
     [["payload", "hasCrashEnvironment"], false],
-    [["payload", "metadata", "k1"], "v3"],
+    [["payload", "metadata", "ProductName"], productName],
+    [["payload", "metadata", "ProductID"], productId],
   ]);
   Assert.ok(found, "Telemetry ping submitted for found crash");
   Assert.ok(found.environment, "There is an environment");
 
   count = yield m.aggregateEventsFiles();
   Assert.equal(count, 0);
 });
 
@@ -435,16 +453,38 @@ add_task(function* test_addCrash() {
 
   crash = map.get("changing-item");
   Assert.ok(!!crash);
   Assert.equal(crash.crashDate, DUMMY_DATE_2);
   Assert.equal(crash.type, m.PROCESS_TYPE_CONTENT + "-" + m.CRASH_TYPE_HANG);
   Assert.ok(crash.isOfType(m.PROCESS_TYPE_CONTENT, m.CRASH_TYPE_HANG));
 });
 
+add_task(function* test_content_crash_ping() {
+  let ac = new TelemetryArchiveTesting.Checker();
+  yield ac.promiseInit();
+
+  let m = yield getManager();
+  let id = yield m.createDummyDump();
+  yield m.addCrash(m.PROCESS_TYPE_CONTENT, m.CRASH_TYPE_CRASH, id, DUMMY_DATE, {
+    StackTraces: stackTraces,
+    ThisShouldNot: "end-up-in-the-ping"
+  });
+  yield m._pingPromise;
+
+  let found = yield ac.promiseFindPing("crash", [
+    [["payload", "crashId"], id],
+    [["payload", "processType"], m.PROCESS_TYPE_CONTENT],
+    [["payload", "stackTraces", "status"], "OK"],
+  ]);
+  Assert.ok(found, "Telemetry ping submitted for content crash");
+  Assert.equal(found.payload.metadata.ThisShouldNot, undefined,
+               "Non-whitelisted fields should be filtered out");
+});
+
 add_task(function* test_generateSubmissionID() {
   let m = yield getManager();
 
   const SUBMISSION_ID_REGEX =
     /^(sub-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
   let id = m.generateSubmissionID();
   Assert.ok(SUBMISSION_ID_REGEX.test(id));
 });
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -92,61 +92,99 @@ LoginManagerPromptFactory.prototype = {
       let httpHostname = hostname.replace(/^https:\/\//, "http://");
       hasLogins = (prompter._pwmgr.countLogins(httpHostname, null, httpRealm) > 0);
     }
     if (hasLogins && prompter._pwmgr.uiBusy) {
       this.log("_doAsyncPrompt:run bypassed, master password UI busy");
       return;
     }
 
-    this._asyncPromptInProgress = true;
-    prompt.inProgress = true;
+    // Allow only a limited number of authentication dialogs when they are all
+    // canceled by the user.
+    var cancelationCounter = (prompter._browser && prompter._browser.canceledAuthenticationPromptCounter) || { count: 0, id: 0 };
+    if (prompt.channel) {
+      var httpChannel = prompt.channel.QueryInterface(Ci.nsIHttpChannel);
+      if (httpChannel) {
+        var windowId = httpChannel.topLevelContentWindowId;
+        if (windowId != cancelationCounter.id) {
+          // window has been reloaded or navigated, reset the counter
+          cancelationCounter = { count: 0, id: windowId };
+        }
+      }
+    }
 
     var self = this;
 
     var runnable = {
+      cancel: false,
       run : function() {
         var ok = false;
-        try {
-          self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
-          ok = prompter.promptAuth(prompt.channel,
-                                   prompt.level,
-                                   prompt.authInfo);
-        } catch (e) {
-          if (e instanceof Components.Exception &&
-              e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
-            self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
+        if (!this.cancel) {
+          try {
+            self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
+            ok = prompter.promptAuth(prompt.channel,
+                                     prompt.level,
+                                     prompt.authInfo);
+          } catch (e) {
+            if (e instanceof Components.Exception &&
+                e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+              self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
+            } else {
+              Components.utils.reportError("LoginManagerPrompter: " +
+                                           "_doAsyncPrompt:run: " + e + "\n");
+            }
+          }
+
+          delete self._asyncPrompts[hashKey];
+          prompt.inProgress = false;
+          self._asyncPromptInProgress = false;
+
+          if (ok) {
+            cancelationCounter.count = 0;
           } else {
-            Components.utils.reportError("LoginManagerPrompter: " +
-                                         "_doAsyncPrompt:run: " + e + "\n");
+            cancelationCounter.count++;
+          }
+          if (prompter._browser) {
+            prompter._browser.canceledAuthenticationPromptCounter = cancelationCounter;
           }
         }
 
-        delete self._asyncPrompts[hashKey];
-        prompt.inProgress = false;
-        self._asyncPromptInProgress = false;
-
         for (var consumer of prompt.consumers) {
           if (!consumer.callback)
             // Not having a callback means that consumer didn't provide it
             // or canceled the notification
             continue;
 
           self.log("Calling back to " + consumer.callback + " ok=" + ok);
           try {
-            if (ok)
+            if (ok) {
               consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
-            else
-              consumer.callback.onAuthCancelled(consumer.context, true);
+            } else {
+              consumer.callback.onAuthCancelled(consumer.context, !this.cancel);
+            }
           } catch (e) { /* Throw away exceptions caused by callback */ }
         }
         self._doAsyncPrompt();
       }
     };
 
+    var cancelDialogLimit = Services.prefs.getIntPref("prompts.authentication_dialog_abuse_limit");
+
+    this.log("cancelationCounter =", cancelationCounter);
+    if (cancelDialogLimit && cancelationCounter.count >= cancelDialogLimit) {
+      this.log("Blocking auth dialog, due to exceeding dialog bloat limit");
+      delete this._asyncPrompts[hashKey];
+
+      // just make the runnable cancel all consumers
+      runnable.cancel = true;
+    } else {
+      this._asyncPromptInProgress = true;
+      prompt.inProgress = true;
+    }
+
     Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
     this.log("_doAsyncPrompt:run dispatched");
   },
 
 
   _cancelPendingPrompts : function() {
     this.log("Canceling all pending prompts...");
     var asyncPrompts = this._asyncPrompts;
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -138,18 +138,16 @@ const DEFAULT_ENVIRONMENT_PREFS = new Ma
   ["browser.newtabpage.enhanced", {what: RECORD_PREF_VALUE}],
   ["browser.shell.checkDefaultBrowser", {what: RECORD_PREF_VALUE}],
   ["browser.search.suggest.enabled", {what: RECORD_PREF_VALUE}],
   ["browser.startup.homepage", {what: RECORD_PREF_STATE}],
   ["browser.startup.page", {what: RECORD_PREF_VALUE}],
   ["browser.tabs.animate", {what: RECORD_PREF_VALUE}],
   ["browser.urlbar.suggest.searches", {what: RECORD_PREF_VALUE}],
   ["browser.urlbar.userMadeSearchSuggestionsChoice", {what: RECORD_PREF_VALUE}],
-  // Record "Zoom Text Only" pref in Firefox 50 to 52 (Bug 979323).
-  ["browser.zoom.full", {what: RECORD_PREF_VALUE}],
   ["devtools.chrome.enabled", {what: RECORD_PREF_VALUE}],
   ["devtools.debugger.enabled", {what: RECORD_PREF_VALUE}],
   ["devtools.debugger.remote-enabled", {what: RECORD_PREF_VALUE}],
   ["dom.ipc.plugins.asyncInit.enabled", {what: RECORD_PREF_VALUE}],
   ["dom.ipc.plugins.enabled", {what: RECORD_PREF_VALUE}],
   ["dom.ipc.processCount", {what: RECORD_PREF_VALUE, requiresRestart: true}],
   ["dom.max_script_run_time", {what: RECORD_PREF_VALUE}],
   ["experiments.manifest.uri", {what: RECORD_PREF_VALUE}],
--- a/toolkit/components/telemetry/docs/data/environment.rst
+++ b/toolkit/components/telemetry/docs/data/environment.rst
@@ -319,17 +319,17 @@ Each key in the object is the name of a 
 The following is a partial list of collected preferences.
 
 - ``browser.search.suggest.enabled``: The "master switch" for search suggestions everywhere in Firefox (search bar, urlbar, etc.). Defaults to true.
 
 - ``browser.urlbar.suggest.searches``: True if search suggestions are enabled in the urlbar. Defaults to false.
 
 - ``browser.urlbar.userMadeSearchSuggestionsChoice``: True if the user has clicked Yes or No in the urlbar's opt-in notification. Defaults to false.
 
-- ``browser.zoom.full``: True if zoom is enabled for both text and images, that is if "Zoom Text Only" is not enabled. Defaults to true. Collection of this preference has been enabled in Firefox 50 and will be disabled again in Firefox 53 (`Bug 979323 <https://bugzilla.mozilla.org/show_bug.cgi?id=979323>`_).
+- ``browser.zoom.full`` (deprecated): True if zoom is enabled for both text and images, that is if "Zoom Text Only" is not enabled. Defaults to true. This preference was collected in Firefox 50 to 52 (`Bug 979323 <https://bugzilla.mozilla.org/show_bug.cgi?id=979323>`_).
 
 - ``security.sandbox.content.level``: The meanings of the values are OS dependent, but 0 means not sandboxed for all OS. Details of the meanings can be found in the `Firefox prefs file <http://hg.mozilla.org/mozilla-central/file/tip/browser/app/profile/firefox.js>`_.
 
 attribution
 ~~~~~~~~~~~
 
 This object contains the attribution data for the product installation.
 
--- a/toolkit/content/tests/browser/browser_crash_previous_frameloader.js
+++ b/toolkit/content/tests/browser/browser_crash_previous_frameloader.js
@@ -1,34 +1,42 @@
 "use strict";
 
 /**
- * Cleans up the .dmp and .extra file from a crash.
+ * Returns the id of the crash minidump.
  *
  * @param subject (nsISupports)
  *        The subject passed through the ipc:content-shutdown
  *        observer notification when a content process crash has
  *        occurred.
+ * @returns {String} The crash dump id.
  */
-function cleanUpMinidump(subject) {
+function getCrashDumpId(subject) {
   Assert.ok(subject instanceof Ci.nsIPropertyBag2,
             "Subject needs to be a nsIPropertyBag2 to clean up properly");
-  let dumpID = subject.getPropertyAsAString("dumpID");
+
+  return subject.getPropertyAsAString("dumpID");
+}
 
-  Assert.ok(dumpID, "There should be a dumpID");
-  if (dumpID) {
+/**
+ * Cleans up the .dmp and .extra file from a crash.
+ *
+ * @param id {String} The crash dump id.
+ */
+function cleanUpMinidump(id) {
+  if (id) {
     let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
     dir.append("minidumps");
 
     let file = dir.clone();
-    file.append(dumpID + ".dmp");
+    file.append(id + ".dmp");
     file.remove(true);
 
     file = dir.clone();
-    file.append(dumpID + ".extra");
+    file.append(id + ".extra");
     file.remove(true);
   }
 }
 
 /**
  * This test ensures that if a remote frameloader crashes after
  * the frameloader owner swaps it out for a new frameloader,
  * that a oop-browser-crashed event is not sent to the new
@@ -99,16 +107,22 @@ add_task(function* test_crash_in_previou
     });
 
     gBrowser.updateBrowserRemoteness(browser, false);
     info("Waiting for content process to go away.");
     let [subject /* , data */] = yield contentProcessGone;
 
     // If we don't clean up the minidump, the harness will
     // complain.
-    cleanUpMinidump(subject);
+    let dumpID = getCrashDumpId(subject);
+
+    Assert.ok(dumpID, "There should be a dumpID");
+    if (dumpID) {
+      yield Services.crashmanager.ensureCrashIsPresent(dumpID);
+      cleanUpMinidump(dumpID);
+    }
 
     info("Content process is gone!");
     Assert.ok(!sawTabCrashed,
               "Should not have seen the oop-browser-crashed event.");
     browser.removeEventListener("oop-browser-crashed", onTabCrashed);
   });
 });
--- a/toolkit/crashreporter/docs/index.rst
+++ b/toolkit/crashreporter/docs/index.rst
@@ -125,16 +125,25 @@ actor should check for it by calling Tak
 Method: see ``mozilla::plugins::PluginModuleParent::ActorDestroy`` and
 ``mozilla::plugins::PluginModuleParent::ProcessFirstMinidump``. That method
 is responsible for calling
 ``mozilla::dom::CrashReporterParent::GenerateCrashReportForMinidump`` with
 appropriate crash annotations specific to the crash. All child-process
 crashes are annotated with a ``ProcessType`` annotation, such as "content" or
 "plugin".
 
+Once the minidump file has been generated the
+``mozilla::dom::CrashReporterHost`` is notified of the crash. It will first
+try to extract the stack traces from the minidump file using the
+*minidump analyzer*. Then the stack traces will be stored in the extra file
+together with the rest of the crash annotations and finally the crash will be
+recorded by calling ```CrashService.addCrash()```. This last step adds the
+crash to the ```CrashManager``` database and automatically sends a crash ping
+with information about the crash.
+
 Submission of child process crashes is handled by application code. This
 code prompts the user to submit crashes in context-appropriate UI and then
 submits the crashes using ``CrashSubmit.jsm``.
 
 Memory Reports
 ==============
 
 When a process detects that it is running low on memory, a memory report is
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -3,16 +3,17 @@
 /* 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 "nsExceptionHandler.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryService.h"
 #include "nsDataHashtable.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/dom/CrashReporterChild.h"
 #include "mozilla/ipc/CrashReporterClient.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Sprintf.h"
@@ -124,16 +125,17 @@ namespace CrashReporter {
 #ifdef XP_WIN32
 typedef wchar_t XP_CHAR;
 typedef std::wstring xpstring;
 #define XP_TEXT(x) L##x
 #define CONVERT_XP_CHAR_TO_UTF16(x) x
 #define XP_STRLEN(x) wcslen(x)
 #define my_strlen strlen
 #define CRASH_REPORTER_FILENAME "crashreporter.exe"
+#define MINIDUMP_ANALYZER_FILENAME "minidump-analyzer.exe"
 #define PATH_SEPARATOR "\\"
 #define XP_PATH_SEPARATOR L"\\"
 #define XP_PATH_SEPARATOR_CHAR L'\\'
 #define XP_PATH_MAX (MAX_PATH + 1)
 // "<reporter path>" "<minidump path>"
 #define CMDLINE_SIZE ((XP_PATH_MAX * 2) + 6)
 #ifdef _USE_32BIT_TIME_T
 #define XP_TTOA(time, buffer, base) ltoa(time, buffer, base)
@@ -142,16 +144,17 @@ typedef std::wstring xpstring;
 #endif
 #define XP_STOA(size, buffer, base) _ui64toa(size, buffer, base)
 #else
 typedef char XP_CHAR;
 typedef std::string xpstring;
 #define XP_TEXT(x) x
 #define CONVERT_XP_CHAR_TO_UTF16(x) NS_ConvertUTF8toUTF16(x)
 #define CRASH_REPORTER_FILENAME "crashreporter"
+#define MINIDUMP_ANALYZER_FILENAME "minidump-analyzer"
 #define PATH_SEPARATOR "/"
 #define XP_PATH_SEPARATOR "/"
 #define XP_PATH_SEPARATOR_CHAR '/'
 #define XP_PATH_MAX PATH_MAX
 #ifdef XP_LINUX
 #define XP_STRLEN(x) my_strlen(x)
 #define XP_TTOA(time, buffer, base) my_inttostring(time, buffer, sizeof(buffer))
 #define XP_STOA(size, buffer, base) my_inttostring(size, buffer, sizeof(buffer))
@@ -178,51 +181,26 @@ typedef std::string xpstring;
 static const XP_CHAR dumpFileExtension[] = XP_TEXT(".dmp");
 #endif
 
 static const XP_CHAR childCrashAnnotationBaseName[] = XP_TEXT("GeckoChildCrash");
 static const XP_CHAR extraFileExtension[] = XP_TEXT(".extra");
 static const XP_CHAR memoryReportExtension[] = XP_TEXT(".memory.json.gz");
 static xpstring *defaultMemoryReportPath = nullptr;
 
-// A whitelist of crash annotations which do not contain sensitive data
-// and are saved in the crash record and sent with Firefox Health Report.
-static char const * const kCrashEventAnnotations[] = {
-  "AsyncShutdownTimeout",
-  "BuildID",
-  "ProductID",
-  "ProductName",
-  "ReleaseChannel",
-  "SecondsSinceLastCrash",
-  "ShutdownProgress",
-  "StartupCrash",
-  "TelemetryEnvironment",
-  "Version",
-  // The following entries are not normal annotations but are included
-  // in the crash record/FHR:
-  // "ContainsMemoryReport"
-  // "EventLoopNestingLevel"
-  // "IsGarbageCollecting"
-  // "AvailablePageFile"
-  // "AvailableVirtualMemory"
-  // "SystemMemoryUsePercentage"
-  // "OOMAllocationSize"
-  // "TotalPageFile"
-  // "TotalPhysicalMemory"
-  // "TotalVirtualMemory"
-  // "MozCrashReason"
-};
-
 static const char kCrashMainID[] = "crash.main.2\n";
 
 static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
 
 static XP_CHAR* pendingDirectory;
 static XP_CHAR* crashReporterPath;
 static XP_CHAR* memoryReportPath;
+#if !defined(MOZ_WIDGET_ANDROID)
+static XP_CHAR* minidumpAnalyzerPath;
+#endif // !defined(MOZ_WIDGET_ANDROID)
 
 // Where crash events should go.
 static XP_CHAR* eventsDirectory;
 static char* eventsEnv = nullptr;
 
 // The current telemetry session ID to write to the event file
 static char* currentSessionId = nullptr;
 
@@ -546,20 +524,22 @@ CreatePathFromFile(nsIFile* file)
   if (NS_FAILED(rv)) {
     return nullptr;
   }
   return new xpstring(path.get(), path.Length());
 }
 #endif
 
 static XP_CHAR*
-Concat(XP_CHAR* str, const XP_CHAR* toAppend, int* size)
+Concat(XP_CHAR* str, const XP_CHAR* toAppend, size_t* size)
 {
-  int appendLen = XP_STRLEN(toAppend);
-  if (appendLen >= *size) appendLen = *size - 1;
+  size_t appendLen = XP_STRLEN(toAppend);
+  if (appendLen >= *size) {
+    appendLen = *size - 1;
+  }
 
   memcpy(str, toAppend, appendLen * sizeof(XP_CHAR));
   str += appendLen;
   *str = '\0';
   *size -= appendLen;
 
   return str;
 }
@@ -769,17 +749,17 @@ WriteAnnotation(PlatformWriter& pw, cons
  * dump file path.
  */
 static void
 OpenAPIData(PlatformWriter& aWriter,
             const XP_CHAR* dump_path, const XP_CHAR* minidump_id = nullptr
            )
 {
   static XP_CHAR extraDataPath[XP_PATH_MAX];
-  int size = XP_PATH_MAX;
+  size_t size = XP_PATH_MAX;
   XP_CHAR* p;
   if (minidump_id) {
     p = Concat(extraDataPath, dump_path, &size);
     p = Concat(p, XP_PATH_SEPARATOR, &size);
     p = Concat(p, minidump_id, &size);
   } else {
     p = Concat(extraDataPath, dump_path, &size);
     // Skip back past the .dmp extension, if any.
@@ -820,16 +800,156 @@ WriteGlobalMemoryStatus(PlatformWriter* 
     WRITE_STATEX_FIELD(ullTotalPhys, "TotalPhysicalMemory", _ui64toa);
     WRITE_STATEX_FIELD(ullAvailPhys, "AvailablePhysicalMemory", _ui64toa);
 
 #undef WRITE_STATEX_FIELD
   }
 }
 #endif
 
+#if !defined(MOZ_WIDGET_ANDROID)
+
+/**
+ * Launches the program specified in aProgramPath with aMinidumpPath as its
+ * sole argument.
+ *
+ * @param aProgramPath The path of the program to be launched
+ * @param aMinidumpPath The path of the minidump file, passed as an argument
+ *        to the launched program
+ * @param aWait If true wait for the program termination
+ */
+static bool
+LaunchProgram(const XP_CHAR* aProgramPath, const XP_CHAR* aMinidumpPath,
+              bool aWait = false)
+{
+#ifdef XP_WIN
+  XP_CHAR cmdLine[CMDLINE_SIZE];
+  XP_CHAR* p;
+
+  size_t size = CMDLINE_SIZE;
+  p = Concat(cmdLine, L"\"", &size);
+  p = Concat(p, aProgramPath, &size);
+  p = Concat(p, L"\" \"", &size);
+  p = Concat(p, aMinidumpPath, &size);
+  Concat(p, L"\"", &size);
+
+  PROCESS_INFORMATION pi = {};
+  STARTUPINFO si = {};
+  si.cb = sizeof(si);
+
+  // If CreateProcess() fails don't do anything
+  if (CreateProcess(nullptr, (LPWSTR)cmdLine, nullptr, nullptr, FALSE,
+                    NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
+                    nullptr, nullptr, &si, &pi)) {
+    if (aWait) {
+      WaitForSingleObject(pi.hProcess, INFINITE);
+    }
+
+    CloseHandle(pi.hProcess);
+    CloseHandle(pi.hThread);
+  }
+#elif defined(XP_UNIX)
+#ifdef XP_MACOSX
+  pid_t pid = 0;
+  char* const my_argv[] = {
+    const_cast<char*>(aProgramPath),
+    const_cast<char*>(aMinidumpPath),
+    nullptr
+  };
+
+  char **env = nullptr;
+  char ***nsEnv = _NSGetEnviron();
+  if (nsEnv)
+    env = *nsEnv;
+
+  int rv = posix_spawnp(&pid, my_argv[0], nullptr, &spawnattr, my_argv, env);
+
+  if (rv != 0) {
+    return false;
+  } else if (aWait) {
+    waitpid(pid, nullptr, 0);
+  }
+
+#else // !XP_MACOSX
+  pid_t pid = sys_fork();
+
+  if (pid == -1) {
+    return false;
+  } else if (pid == 0) {
+    // need to clobber this, as libcurl might load NSS,
+    // and we want it to load the system NSS.
+    unsetenv("LD_LIBRARY_PATH");
+    Unused << execl(aProgramPath,
+                    aProgramPath, aMinidumpPath, (char*)0);
+    _exit(1);
+  } else {
+    if (aWait) {
+      sys_waitpid(pid, nullptr, __WALL);
+    }
+  }
+#endif // XP_MACOSX
+#endif // XP_UNIX
+
+  return true;
+}
+
+#else
+
+/**
+ * Launch the crash reporter activity on Android
+ *
+ * @param aProgramPath The path of the program to be launched
+ * @param aMinidumpPath The path to the crash minidump file
+ * @param aSucceeded True if the minidump was obtained successfully
+ */
+
+static bool
+LaunchCrashReporterActivity(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath,
+                            bool aSucceeded)
+{
+  pid_t pid = sys_fork();
+
+  if (pid == -1)
+    return false;
+  else if (pid == 0) {
+    // Invoke the reportCrash activity using am
+    if (androidUserSerial) {
+      Unused << execlp("/system/bin/am",
+                       "/system/bin/am",
+                       "start",
+                       "--user", androidUserSerial,
+                       "-a", "org.mozilla.gecko.reportCrash",
+                       "-n", aProgramPath,
+                       "--es", "minidumpPath", aMinidumpPath,
+                       "--ez", "minidumpSuccess", aSucceeded ? "true" : "false",
+                       (char*)0);
+    } else {
+      Unused << execlp("/system/bin/am",
+                       "/system/bin/am",
+                       "start",
+                       "-a", "org.mozilla.gecko.reportCrash",
+                       "-n", aProgramPath,
+                       "--es", "minidumpPath", aMinidumpPath,
+                       "--ez", "minidumpSuccess", aSucceeded ? "true" : "false",
+                       (char*)0);
+    }
+    _exit(1);
+
+  } else {
+    // We need to wait on the 'am start' command above to finish, otherwise everything will
+    // be killed by the ActivityManager as soon as the signal handler exits
+    int status;
+    Unused << HANDLE_EINTR(sys_waitpid(pid, &status, __WALL));
+  }
+
+  return true;
+}
+
+#endif
+
 bool MinidumpCallback(
 #ifdef XP_LINUX
                       const MinidumpDescriptor& descriptor,
 #else
                       const XP_CHAR* dump_path,
                       const XP_CHAR* minidump_id,
 #endif
                       void* context,
@@ -837,17 +957,17 @@ bool MinidumpCallback(
                       EXCEPTION_POINTERS* exinfo,
                       MDRawAssertionInfo* assertion,
 #endif
                       bool succeeded)
 {
   bool returnValue = showOSCrashReporter ? false : succeeded;
 
   static XP_CHAR minidumpPath[XP_PATH_MAX];
-  int size = XP_PATH_MAX;
+  size_t size = XP_PATH_MAX;
   XP_CHAR* p;
 #ifndef XP_LINUX
   p = Concat(minidumpPath, dump_path, &size);
   p = Concat(p, XP_PATH_SEPARATOR, &size);
   p = Concat(p, minidump_id, &size);
   Concat(p, dumpFileExtension, &size);
 #else
   Concat(minidumpPath, descriptor.path(), &size);
@@ -959,17 +1079,17 @@ bool MinidumpCallback(
 #endif
 
   {
     PlatformWriter apiData;
     PlatformWriter eventFile;
 
     if (eventsDirectory) {
       static XP_CHAR crashEventPath[XP_PATH_MAX];
-      int size = XP_PATH_MAX;
+      size_t size = XP_PATH_MAX;
       XP_CHAR* p;
       p = Concat(crashEventPath, eventsDirectory, &size);
       p = Concat(p, XP_PATH_SEPARATOR, &size);
 #ifdef XP_LINUX
       p = Concat(p, id_ascii, &size);
 #else
       p = Concat(p, minidump_id, &size);
 #endif
@@ -1005,20 +1125,18 @@ bool MinidumpCallback(
 
     if (timeSinceLastCrash != 0) {
       WriteAnnotation(apiData, "SecondsSinceLastCrash",
                       timeSinceLastCrashString);
       WriteAnnotation(eventFile, "SecondsSinceLastCrash",
                       timeSinceLastCrashString);
     }
     if (isGarbageCollecting) {
-      WriteAnnotation(apiData, "IsGarbageCollecting",
-                      isGarbageCollecting ? "1" : "0");
-      WriteAnnotation(eventFile, "IsGarbageCollecting",
-                      isGarbageCollecting ? "1" : "0");
+      WriteAnnotation(apiData, "IsGarbageCollecting", "1");
+      WriteAnnotation(eventFile, "IsGarbageCollecting", "1");
     }
 
     char buffer[128];
 
     if (eventloopNestingLevel > 0) {
       XP_STOA(eventloopNestingLevel, buffer, 10);
       WriteAnnotation(apiData, "EventLoopNestingLevel", buffer);
       WriteAnnotation(eventFile, "EventLoopNestingLevel", buffer);
@@ -1074,118 +1192,32 @@ bool MinidumpCallback(
     }
 
     if (memoryReportPath) {
       WriteLiteral(apiData, "ContainsMemoryReport=1\n");
       WriteLiteral(eventFile, "ContainsMemoryReport=1\n");
     }
   }
 
+  if (!doReport) {
 #ifdef XP_WIN
-  if (!doReport) {
     TerminateProcess(GetCurrentProcess(), 1);
-    return returnValue;
-  }
-
-  XP_CHAR cmdLine[CMDLINE_SIZE];
-  size = CMDLINE_SIZE;
-  p = Concat(cmdLine, L"\"", &size);
-  p = Concat(p, crashReporterPath, &size);
-  p = Concat(p, L"\" \"", &size);
-  p = Concat(p, minidumpPath, &size);
-  Concat(p, L"\"", &size);
-
-  STARTUPINFO si;
-  PROCESS_INFORMATION pi;
-
-  ZeroMemory(&si, sizeof(si));
-  si.cb = sizeof(si);
-  si.dwFlags = STARTF_USESHOWWINDOW;
-  si.wShowWindow = SW_SHOWNORMAL;
-  ZeroMemory(&pi, sizeof(pi));
-
-  if (CreateProcess(nullptr, (LPWSTR)cmdLine, nullptr, nullptr, FALSE, 0,
-                    nullptr, nullptr, &si, &pi)) {
-    CloseHandle( pi.hProcess );
-    CloseHandle( pi.hThread );
-  }
-  // we're not really in a position to do anything if the CreateProcess fails
-  TerminateProcess(GetCurrentProcess(), 1);
-#elif defined(XP_UNIX)
-  if (!doReport) {
+#endif // XP_WIN
     return returnValue;
   }
 
-#ifdef XP_MACOSX
-  char* const my_argv[] = {
-    crashReporterPath,
-    minidumpPath,
-    nullptr
-  };
-
-  char **env = nullptr;
-  char ***nsEnv = _NSGetEnviron();
-  if (nsEnv)
-    env = *nsEnv;
-  int result = posix_spawnp(nullptr,
-                            my_argv[0],
-                            nullptr,
-                            &spawnattr,
-                            my_argv,
-                            env);
-
-  if (result != 0)
-    return false;
-
-#else // !XP_MACOSX
-  pid_t pid = sys_fork();
-
-  if (pid == -1)
-    return false;
-  else if (pid == 0) {
-#if !defined(MOZ_WIDGET_ANDROID)
-    // need to clobber this, as libcurl might load NSS,
-    // and we want it to load the system NSS.
-    unsetenv("LD_LIBRARY_PATH");
-    Unused << execl(crashReporterPath,
-                    crashReporterPath, minidumpPath, (char*)0);
-#else
-    // Invoke the reportCrash activity using am
-    if (androidUserSerial) {
-      Unused << execlp("/system/bin/am",
-                       "/system/bin/am",
-                       "start",
-                       "--user", androidUserSerial,
-                       "-a", "org.mozilla.gecko.reportCrash",
-                       "-n", crashReporterPath,
-                       "--es", "minidumpPath", minidumpPath,
-                       "--ez", "minidumpSuccess", succeeded ? "true" : "false",
-                       (char*)0);
-    } else {
-      Unused << execlp("/system/bin/am",
-                       "/system/bin/am",
-                       "start",
-                       "-a", "org.mozilla.gecko.reportCrash",
-                       "-n", crashReporterPath,
-                       "--es", "minidumpPath", minidumpPath,
-                       "--ez", "minidumpSuccess", succeeded ? "true" : "false",
-                       (char*)0);
-    }
+#if defined(MOZ_WIDGET_ANDROID) // Android
+  returnValue = LaunchCrashReporterActivity(crashReporterPath, minidumpPath,
+                                            succeeded);
+#else // Windows, Mac, Linux, etc...
+  returnValue = LaunchProgram(crashReporterPath, minidumpPath);
+#ifdef XP_WIN
+  TerminateProcess(GetCurrentProcess(), 1);
 #endif
-    _exit(1);
-#ifdef MOZ_WIDGET_ANDROID
-  } else {
-    // We need to wait on the 'am start' command above to finish, otherwise everything will
-    // be killed by the ActivityManager as soon as the signal handler exits
-    int status;
-    Unused << HANDLE_EINTR(sys_waitpid(pid, &status, __WALL));
 #endif
-  }
-#endif // XP_MACOSX
-#endif // XP_UNIX
 
   return returnValue;
 }
 
 #if defined(XP_MACOSX) || defined(__ANDROID__) || defined(XP_LINUX)
 static size_t
 EnsureTrailingSlash(XP_CHAR* aBuf, size_t aBufLen)
 {
@@ -1250,32 +1282,32 @@ BuildTempPath(char* aBuf, size_t aBufLen
 static size_t
 BuildTempPath(char* aBuf, size_t aBufLen)
 {
   // GeckoAppShell or Gonk's init.rc sets this in the environment
   const char *tempenv = PR_GetEnv("TMPDIR");
   if (!tempenv) {
     return false;
   }
-  int size = (int)aBufLen;
+  size_t size = aBufLen;
   Concat(aBuf, tempenv, &size);
   return EnsureTrailingSlash(aBuf, aBufLen);
 }
 
 #elif defined(XP_UNIX)
 
 static size_t
 BuildTempPath(char* aBuf, size_t aBufLen)
 {
   const char *tempenv = PR_GetEnv("TMPDIR");
   const char *tmpPath = "/tmp/";
   if (!tempenv) {
     tempenv = tmpPath;
   }
-  int size = (int)aBufLen;
+  size_t size = aBufLen;
   Concat(aBuf, tempenv, &size);
   return EnsureTrailingSlash(aBuf, aBufLen);
 }
 
 #else
 #error "Implement this for your platform"
 #endif
 
@@ -1302,17 +1334,17 @@ BuildTempPath(PathStringT& aResult)
 
 static void
 PrepareChildExceptionTimeAnnotations()
 {
   MOZ_ASSERT(!XRE_IsParentProcess());
   static XP_CHAR tempPath[XP_PATH_MAX] = {0};
 
   // Get the temp path
-  int charsAvailable = XP_PATH_MAX;
+  size_t charsAvailable = XP_PATH_MAX;
   XP_CHAR* p = tempPath;
 #if (defined(XP_MACOSX) || defined(XP_WIN))
   if (!childProcessTmpDir || childProcessTmpDir->empty()) {
     return;
   }
   p = Concat(p, childProcessTmpDir->c_str(), &charsAvailable);
   // Ensure that this path ends with a path separator
   if (p > tempPath && *(p - 1) != XP_PATH_SEPARATOR_CHAR) {
@@ -1515,16 +1547,42 @@ ChildFilter(void* context)
 {
   bool result = Filter(context);
   if (result) {
     PrepareChildExceptionTimeAnnotations();
   }
   return result;
 }
 
+#if !defined(MOZ_WIDGET_ANDROID)
+
+// Locate the specified executable and store its path as a native string in
+// the |aPathPtr| so we can later invoke it from within the exception handler.
+static nsresult
+LocateExecutable(nsIFile* aXREDirectory, const nsACString& aName,
+                 nsAString& aPath)
+{
+  nsCOMPtr<nsIFile> exePath;
+  nsresult rv = aXREDirectory->Clone(getter_AddRefs(exePath));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef XP_MACOSX
+  exePath->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
+  exePath->Append(NS_LITERAL_STRING("crashreporter.app"));
+  exePath->Append(NS_LITERAL_STRING("Contents"));
+  exePath->Append(NS_LITERAL_STRING("MacOS"));
+#endif
+
+  exePath->AppendNative(aName);
+  exePath->GetPath(aPath);
+  return NS_OK;
+}
+
+#endif // !defined(MOZ_WIDGET_ANDROID)
+
 nsresult SetExceptionHandler(nsIFile* aXREDirectory,
                              bool force/*=false*/)
 {
   if (gExceptionHandler)
     return NS_ERROR_ALREADY_INITIALIZED;
 
 #if !defined(DEBUG) || defined(MOZ_WIDGET_GONK)
   // In non-debug builds, enable the crash reporter by default, and allow
@@ -1565,54 +1623,57 @@ nsresult SetExceptionHandler(nsIFile* aX
   crashReporterAPIData_Hash =
     new nsDataHashtable<nsCStringHashKey,nsCString>();
   NS_ENSURE_TRUE(crashReporterAPIData_Hash, NS_ERROR_OUT_OF_MEMORY);
 
   notesField = new nsCString();
   NS_ENSURE_TRUE(notesField, NS_ERROR_OUT_OF_MEMORY);
 
   if (!headlessClient) {
-    // locate crashreporter executable
-    nsCOMPtr<nsIFile> exePath;
-    nsresult rv = aXREDirectory->Clone(getter_AddRefs(exePath));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-#if defined(XP_MACOSX)
-    exePath->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
-    exePath->Append(NS_LITERAL_STRING("crashreporter.app"));
-    exePath->Append(NS_LITERAL_STRING("Contents"));
-    exePath->Append(NS_LITERAL_STRING("MacOS"));
-#endif
-
-    exePath->AppendNative(NS_LITERAL_CSTRING(CRASH_REPORTER_FILENAME));
+#if !defined(MOZ_WIDGET_ANDROID)
+    // Locate the crash reporter executable
+    nsAutoString crashReporterPath_temp;
+    nsresult rv = LocateExecutable(aXREDirectory,
+                                   NS_LITERAL_CSTRING(CRASH_REPORTER_FILENAME),
+                                   crashReporterPath_temp);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsAutoString minidumpAnalyzerPath_temp;
+    rv = LocateExecutable(aXREDirectory,
+                          NS_LITERAL_CSTRING(MINIDUMP_ANALYZER_FILENAME),
+                          minidumpAnalyzerPath_temp);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
 
 #ifdef XP_WIN32
-    nsString crashReporterPath_temp;
-
-    exePath->GetPath(crashReporterPath_temp);
-    crashReporterPath = reinterpret_cast<wchar_t*>(ToNewUnicode(crashReporterPath_temp));
-#elif !defined(__ANDROID__)
-    nsCString crashReporterPath_temp;
-
-    exePath->GetNativePath(crashReporterPath_temp);
-    crashReporterPath = ToNewCString(crashReporterPath_temp);
+  crashReporterPath =
+    reinterpret_cast<wchar_t*>(ToNewUnicode(crashReporterPath_temp));
+  minidumpAnalyzerPath =
+    reinterpret_cast<wchar_t*>(ToNewUnicode(minidumpAnalyzerPath_temp));
+#else
+  crashReporterPath = ToNewCString(crashReporterPath_temp);
+  minidumpAnalyzerPath = ToNewCString(minidumpAnalyzerPath_temp);
+#endif // XP_WIN32
 #else
     // On Android, we launch using the application package name instead of a
     // filename, so use the dynamically set MOZ_ANDROID_PACKAGE_NAME, or fall
     // back to the static ANDROID_PACKAGE_NAME.
     const char* androidPackageName = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME");
     if (androidPackageName != nullptr) {
       nsCString package(androidPackageName);
       package.Append("/org.mozilla.gecko.CrashReporter");
       crashReporterPath = ToNewCString(package);
     } else {
       nsCString package(ANDROID_PACKAGE_NAME "/org.mozilla.gecko.CrashReporter");
       crashReporterPath = ToNewCString(package);
     }
-#endif
+#endif // !defined(MOZ_WIDGET_ANDROID)
   }
 
   // get temp path to use for minidump path
 #if defined(XP_WIN32)
   nsString tempPath;
 #else
   nsCString tempPath;
 #endif
@@ -2114,27 +2175,16 @@ static void ReplaceChar(nsCString& str, 
     str.Replace(pos - 1, 1, replacement);
 
     str.BeginReading(iter);
     iter.advance(pos + replacement.Length() - 1);
     str.EndReading(end);
   }
 }
 
-static bool
-IsInWhitelist(const nsACString& key)
-{
-  for (size_t i = 0; i < ArrayLength(kCrashEventAnnotations); ++i) {
-    if (key.EqualsASCII(kCrashEventAnnotations[i])) {
-      return true;
-    }
-  }
-  return false;
-}
-
 // This function is miscompiled with MSVC 2005/2008 when PGO is on.
 #ifdef _MSC_VER
 #pragma optimize("", off)
 #endif
 static nsresult
 EscapeAnnotation(const nsACString& key, const nsACString& data, nsCString& escapedData)
 {
   if (FindInReadable(NS_LITERAL_CSTRING("="), key) ||
@@ -2261,19 +2311,17 @@ nsresult AnnotateCrashReport(const nsACS
     const nsACString& key = it.Key();
     nsCString entry = it.Data();
     if (!entry.IsEmpty()) {
       NS_NAMED_LITERAL_CSTRING(kEquals, "=");
       NS_NAMED_LITERAL_CSTRING(kNewline, "\n");
       nsAutoCString line = key + kEquals + entry + kNewline;
 
       crashReporterAPIData->Append(line);
-      if (IsInWhitelist(key)) {
-        crashEventAPIData->Append(line);
-      }
+      crashEventAPIData->Append(line);
     }
   }
 
   return NS_OK;
 }
 
 nsresult RemoveCrashReportAnnotation(const nsACString& key)
 {
@@ -3025,16 +3073,44 @@ bool
 AppendExtraData(const nsAString& id, const AnnotationTable& data)
 {
   nsCOMPtr<nsIFile> extraFile;
   if (!GetExtraFileForID(id, getter_AddRefs(extraFile)))
     return false;
   return AppendExtraData(extraFile, data);
 }
 
+/**
+ * Runs the minidump analyzer program on the specified crash dump. The analyzer
+ * will extract the stack traces from the dump and store them in JSON format as
+ * an annotation in the extra file associated with the crash.
+ *
+ * This method waits synchronously for the program to have finished executing,
+ * do not call it from the main thread!
+ */
+void
+RunMinidumpAnalyzer(const nsAString& id)
+{
+#if !defined(MOZ_WIDGET_ANDROID)
+  nsCOMPtr<nsIFile> file;
+
+  if (CrashReporter::GetMinidumpForID(id, getter_AddRefs(file)) && file) {
+#ifdef XP_WIN
+    nsAutoString path;
+    file->GetPath(path);
+#else
+    nsAutoCString path;
+    file->GetNativePath(path);
+#endif
+
+    LaunchProgram(minidumpAnalyzerPath, path.get(), /* aWait */ true);
+  }
+#endif // !defined(MOZ_WIDGET_ANDROID)
+}
+
 //-----------------------------------------------------------------------------
 // Helpers for AppendExtraData()
 //
 struct Blacklist {
   Blacklist() : mItems(nullptr), mLen(0) { }
   Blacklist(const char** items, int len) : mItems(items), mLen(len) { }
 
   bool Contains(const nsACString& key) const {
--- a/toolkit/crashreporter/nsExceptionHandler.h
+++ b/toolkit/crashreporter/nsExceptionHandler.h
@@ -98,16 +98,17 @@ typedef nsDataHashtable<nsCStringHashKey
 
 void DeleteMinidumpFilesForID(const nsAString& id);
 bool GetMinidumpForID(const nsAString& id, nsIFile** minidump);
 bool GetIDFromMinidump(nsIFile* minidump, nsAString& id);
 bool GetExtraFileForID(const nsAString& id, nsIFile** extraFile);
 bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile);
 bool AppendExtraData(const nsAString& id, const AnnotationTable& data);
 bool AppendExtraData(nsIFile* extraFile, const AnnotationTable& data);
+void RunMinidumpAnalyzer(const nsAString& id);
 
 /*
  * Renames the stand alone dump file aDumpFile to:
  *  |aOwnerDumpFile-aDumpFileProcessType.dmp|
  * and moves it into the same directory as aOwnerDumpFile. Does not
  * modify aOwnerDumpFile in any way.
  *
  * @param aDumpFile - the dump file to associate with aOwnerDumpFile.
--- a/toolkit/crashreporter/test/unit/head_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/head_crashreporter.js
@@ -1,9 +1,13 @@
-Components.utils.import("resource://gre/modules/osfile.jsm");
+var {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://testing-common/AppData.jsm", this);
 
 function getEventDir() {
   return OS.Path.join(do_get_tempdir().path, "crash-events");
 }
 
 /*
  * Run an xpcshell subprocess and crash it.
  *
@@ -80,88 +84,108 @@ function do_crash(setup, callback, canRe
   if (!canReturnZero) {
     // should exit with an error (should have crashed)
     do_check_neq(process.exitValue, 0);
   }
 
   handleMinidump(callback);
 }
 
-function handleMinidump(callback)
-{
-  // find minidump
-  let minidump = null;
+function getMinidump() {
   let en = do_get_tempdir().directoryEntries;
   while (en.hasMoreElements()) {
     let f = en.getNext().QueryInterface(Components.interfaces.nsILocalFile);
     if (f.leafName.substr(-4) == ".dmp") {
-      minidump = f;
-      break;
+      return f;
     }
   }
 
-  if (minidump == null)
+  return null;
+}
+
+function handleMinidump(callback)
+{
+  // find minidump
+  let minidump = getMinidump();
+
+  if (minidump == null) {
     do_throw("No minidump found!");
+  }
 
   let extrafile = minidump.clone();
   extrafile.leafName = extrafile.leafName.slice(0, -4) + ".extra";
 
   let memoryfile = minidump.clone();
   memoryfile.leafName = memoryfile.leafName.slice(0, -4) + ".memory.json.gz";
 
   // Just in case, don't let these files linger.
   do_register_cleanup(function() {
-          if (minidump.exists())
-              minidump.remove(false);
-          if (extrafile.exists())
-              extrafile.remove(false);
-          if (memoryfile.exists())
-              memoryfile.remove(false);
-      });
+    if (minidump.exists()) {
+      minidump.remove(false);
+    }
+    if (extrafile.exists()) {
+      extrafile.remove(false);
+    }
+    if (memoryfile.exists()) {
+      memoryfile.remove(false);
+    }
+  });
+
   do_check_true(extrafile.exists());
   let extra = parseKeyValuePairsFromFile(extrafile);
 
-  if (callback)
+  if (callback) {
     callback(minidump, extra);
+  }
 
-  if (minidump.exists())
+  if (minidump.exists()) {
     minidump.remove(false);
-  if (extrafile.exists())
+  }
+  if (extrafile.exists()) {
     extrafile.remove(false);
-  if (memoryfile.exists())
+  }
+  if (memoryfile.exists()) {
     memoryfile.remove(false);
+  }
 }
 
 function do_content_crash(setup, callback)
 {
   do_load_child_test_harness();
   do_test_pending();
 
   // Setting the minidump path won't work in the child, so we need to do
   // that here.
   let crashReporter =
       Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
-      .getService(Components.interfaces.nsICrashReporter);
+                .getService(Components.interfaces.nsICrashReporter);
   crashReporter.minidumpPath = do_get_tempdir();
 
   let headfile = do_get_file("../unit/crasher_subprocess_head.js");
   let tailfile = do_get_file("../unit/crasher_subprocess_tail.js");
   if (setup) {
-    if (typeof(setup) == "function")
+    if (typeof(setup) == "function") {
       // funky, but convenient
       setup = "(" + setup.toSource() + ")();";
+    }
   }
 
   let handleCrash = function() {
-    try {
-      handleMinidump(callback);
-    } catch (x) {
-      do_report_unexpected_exception(x);
-    }
-    do_test_finished();
+    do_get_profile();
+    makeFakeAppDir().then(() => {
+      let id = getMinidump().leafName.slice(0, -4);
+      return Services.crashmanager.ensureCrashIsPresent(id);
+    }).then(() => {
+      try {
+        handleMinidump(callback);
+      } catch (x) {
+        do_report_unexpected_exception(x);
+      }
+      do_test_finished();
+    });
   };
 
   sendCommand("load(\"" + headfile.path.replace(/\\/g, "/") + "\");", () =>
     sendCommand(setup, () =>
       sendCommand("load(\"" + tailfile.path.replace(/\\/g, "/") + "\");", () =>
         do_execute_soon(handleCrash)
       )
     )
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -61,14 +61,16 @@ skip-if = toolkit == 'android'
 skip-if = toolkit == 'android'
 [test_sqlite_shutdown.js]
 skip-if = toolkit == 'android'
 [test_task.js]
 skip-if = toolkit == 'android'
 [test_timer.js]
 skip-if = toolkit == 'android'
 [test_UpdateUtils_url.js]
+skip-if = !updater
+reason = LOCALE is not defined without MOZ_UPDATER
 [test_UpdateUtils_updatechannel.js]
 [test_web_channel.js]
 [test_web_channel_broker.js]
 [test_ZipUtils.js]
 skip-if = toolkit == 'android'
 [test_Log_stackTrace.js]