Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 14 Aug 2015 08:59:59 -0400
changeset 257781 641235287674dd61ba38e72cc724763de74897a7
parent 257710 d9bd5188616644238ef37921e43c124926781dce (current diff)
parent 257780 0760af2a400f9bb1b86449d52bcf41926ab58288 (diff)
child 257782 1b24022474297b294f3ccb3f0f9d7e477ea2c2ae
push id29226
push userryanvm@gmail.com
push dateFri, 14 Aug 2015 13:01:14 +0000
treeherdermozilla-central@1b2402247429 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c. a=merge
image/ImageMetadata.cpp
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1181,17 +1181,17 @@ nsContextMenu.prototype = {
 #endif
     };
 
     mm.addMessageListener("ContextMenu:SetAsDesktopBackground:Result", onMessage);
   },
 
   // Save URL of clicked-on frame.
   saveFrame: function () {
-    saveDocument(this.target.ownerDocument);
+    saveBrowser(this.browser, false, this.frameOuterWindowID);
   },
 
   // Helper function to wait for appropriate MIME-type headers and
   // then prompt the user with a file picker
   saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc, docURI,
                        windowID, linkDownload) {
     // canonical def in nsURILoader.h
     const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -50,16 +50,18 @@ support-files =
   file_bug906190_3_4.html
   file_bug906190_redirected.html
   file_bug906190.js
   file_bug906190.sjs
   file_mediaPlayback.html
   file_mixedContentFromOnunload.html
   file_mixedContentFromOnunload_test1.html
   file_mixedContentFromOnunload_test2.html
+  file_mixedContentFramesOnHttp.html
+  file_mixedPassiveContent.html
   file_bug970276_popup1.html
   file_bug970276_popup2.html
   file_bug970276_favicon1.ico
   file_bug970276_favicon2.ico
   file_documentnavigation_frameset.html
   file_dom_notifications.html
   file_double_close_tab.html
   file_favicon_change.html
@@ -266,16 +268,19 @@ tags = mcb
 [browser_bug880101.js]
 [browser_bug882977.js]
 [browser_bug902156.js]
 tags = mcb
 [browser_bug906190.js]
 tags = mcb
 skip-if = buildapp == "mulet" || e10s # Bug 1093642 - test manipulates content and relies on content focus
 [browser_mixedContentFromOnunload.js]
+tags = mcb
+[browser_mixedContentFramesOnHttp.js]
+tags = mcb
 [browser_bug970746.js]
 [browser_bug1015721.js]
 skip-if = os == 'win' || e10s # Bug 1159268 - Need a content-process safe version of synthesizeWheel
 [browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_bug1070778.js]
 [browser_canonizeURL.js]
 skip-if = e10s # Bug 1094510 - test hits the network in e10s mode only
 [browser_clipboard.js]
@@ -483,16 +488,17 @@ skip-if = (os == "win" && !debug)
 [browser_web_channel.js]
 [browser_windowopen_reflows.js]
 skip-if = buildapp == 'mulet'
 [browser_wyciwyg_urlbarCopying.js]
 [browser_zbug569342.js]
 skip-if = e10s # Bug 1094240 - has findbar-related failures
 [browser_registerProtocolHandler_notification.js]
 [browser_no_mcb_on_http_site.js]
+tags = mcb
 [browser_bug1104165-switchtab-decodeuri.js]
 [browser_bug1003461-switchtab-override.js]
 [browser_bug1024133-switchtab-override-keynav.js]
 [browser_bug1025195_switchToTabHavingURI_aOpenParams.js]
 [browser_addCertException.js]
 skip-if = e10s # Bug 1100687 - test directly manipulates content (content.document.getElementById)
 [browser_bug1045809.js]
 tags = mcb
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js
@@ -0,0 +1,52 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Test for Bug 1182551 -
+ *
+ * This test has a top level HTTP page with an HTTPS iframe.  The HTTPS iframe
+ * includes an HTTP image.  We check that the top level security state is
+ * STATE_IS_INSECURE.  The mixed content from the iframe shouldn't "upgrade"
+ * the HTTP top level page to broken HTTPS.
+ */
+
+const gHttpTestRoot = "http://example.com/browser/browser/base/content/test/general/";
+
+let gTestBrowser = null;
+
+function SecStateTestsCompleted() {
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
+
+function test() {
+  waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["security.mixed_content.block_active_content", true],
+    ["security.mixed_content.block_display_content", false]
+  ]}, SecStateTests);
+}
+
+function SecStateTests() {
+  let url = gHttpTestRoot + "file_mixedContentFramesOnHttp.html";
+  gBrowser.selectedTab = gBrowser.addTab();
+  gTestBrowser = gBrowser.selectedBrowser;
+  whenLoaded(gTestBrowser, SecStateTest1);
+  gTestBrowser.contentWindow.location = url;
+}
+
+// The http page loads an https frame with an http image.
+function SecStateTest1() {
+  // check security state is insecure
+  isSecurityState("insecure");
+
+  SecStateTestsCompleted();
+}
+
+function whenLoaded(aElement, aCallback) {
+  aElement.addEventListener("load", function onLoad() {
+    aElement.removeEventListener("load", onLoad, true);
+    executeSoon(aCallback);
+  }, true);
+}
--- a/browser/base/content/test/general/browser_mixedContentFromOnunload.js
+++ b/browser/base/content/test/general/browser_mixedContentFromOnunload.js
@@ -65,43 +65,14 @@ function SecStateTest2A() {
 }
 
 function SecStateTest2B() {
   isSecurityState("broken");
 
   SecStateTestsCompleted();
 }
 
-// Compares the security state of the page with what is expected
-function isSecurityState(expectedState) {
-  let ui = gTestBrowser.securityUI;
-  if (!ui) {
-    ok(false, "No security UI to get the security state");
-    return;
-  }
-
-  const wpl = Components.interfaces.nsIWebProgressListener;
-
-  // determine the security state
-  let isSecure = ui.state & wpl.STATE_IS_SECURE;
-  let isBroken = ui.state & wpl.STATE_IS_BROKEN;
-  let isInsecure = ui.state & wpl.STATE_IS_INSECURE;
-
-  let actualState;
-  if (isSecure && !(isBroken || isInsecure)) {
-    actualState = "secure";
-  } else if (isBroken && !(isSecure || isInsecure)) {
-    actualState = "broken";
-  } else if (isInsecure && !(isSecure || isBroken)) {
-    actualState = "insecure";
-  } else {
-    actualState = "unknown";
-  }
-
-  is(expectedState, actualState, "Expected state " + expectedState + " and the actual state is " + actualState + ".");
-}
-
 function whenLoaded(aElement, aCallback) {
   aElement.addEventListener("load", function onLoad() {
     aElement.removeEventListener("load", onLoad, true);
     executeSoon(aCallback);
   }, true);
 }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedContentFramesOnHttp.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1182551</title>
+</head>
+<body>
+  <p>Test for Bug 1182551.  This is an HTTP top level page.  We include an HTTPS iframe that loads mixed passive content.</p>
+  <iframe src="https://example.org/browser/browser/base/content/test/general/file_mixedPassiveContent.html"></iframe>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedPassiveContent.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551
+-->
+<head>
+  <meta charset="utf-8">
+  <title>HTTPS page with HTTP image</title>
+</head>
+<body>
+  <img src="http://mochi.test:8888/tests/image/test/mochitest/blue.png">
+</body>
+</html>
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -956,8 +956,37 @@ function promiseNewSearchEngine(basename
       },
       onError: function (errCode) {
         Assert.ok(false, "addEngine failed with error code " + errCode);
         reject();
       },
     });
   });
 }
+
+// Compares the security state of the page with what is expected
+function isSecurityState(expectedState) {
+  let ui = gTestBrowser.securityUI;
+  if (!ui) {
+    ok(false, "No security UI to get the security state");
+    return;
+  }
+
+  const wpl = Components.interfaces.nsIWebProgressListener;
+
+  // determine the security state
+  let isSecure = ui.state & wpl.STATE_IS_SECURE;
+  let isBroken = ui.state & wpl.STATE_IS_BROKEN;
+  let isInsecure = ui.state & wpl.STATE_IS_INSECURE;
+
+  let actualState;
+  if (isSecure && !(isBroken || isInsecure)) {
+    actualState = "secure";
+  } else if (isBroken && !(isSecure || isInsecure)) {
+    actualState = "broken";
+  } else if (isInsecure && !(isSecure || isBroken)) {
+    actualState = "insecure";
+  } else {
+    actualState = "unknown";
+  }
+
+  is(expectedState, actualState, "Expected state " + expectedState + " and the actual state is " + actualState + ".");
+}
--- a/build/annotationProcessors/CodeGenerator.java
+++ b/build/annotationProcessors/CodeGenerator.java
@@ -43,17 +43,17 @@ public class CodeGenerator {
                 "    typedef mozilla::jni::LocalRef<" + unqualifiedName + "> LocalRef;\n" +
                 "    typedef mozilla::jni::GlobalRef<" + unqualifiedName + "> GlobalRef;\n" +
                 "    typedef const mozilla::jni::Param<" + unqualifiedName + ">& Param;\n" +
                 "\n" +
                 "    static constexpr char name[] =\n" +
                 "            \"" + cls.getName().replace('.', '/') + "\";\n" +
                 "\n" +
                 "protected:\n" +
-                "    using Class::Class;\n" +
+                "    " + unqualifiedName + "(jobject instance) : Class(instance) {}\n" +
                 "\n");
 
         cpp.append(
                 "constexpr char " + clsName + "::name[];\n" +
                 "\n");
 
         natives.append(
                 "template<class Impl>\n" +
--- a/dom/apps/tests/file_test_widget.js
+++ b/dom/apps/tests/file_test_widget.js
@@ -165,17 +165,19 @@ function checkIsWidgetScript(testMozbrow
   request.onsuccess = function() {
     var widget = request.result;
     ok(widget, "Should" + (widget ? "" : " not") + " be a widget");
     finish();
   };
   request.onerror = onError;
 
   if (testMozbrowserEvent) {
-    content.window.open("about:blank"); /* test mozbrowseropenwindow */
+    var win = content.window.open("about:blank"); /* test mozbrowseropenwindow */
+    /*Close new window to avoid mochitest "unable to restore focus" failures.*/
+    win.close();
     content.window.scrollTo(4000, 4000); /* test mozbrowser(async)scroll */
   }
 }
 
 var tests = [
   // Permissions
   function() {
     SpecialPowers.pushPermissions(
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3844,16 +3844,40 @@ nsContentUtils::MatchElementId(nsIConten
   if (!id) {
     // OOM, so just bail
     return nullptr;
   }
 
   return MatchElementId(aContent, id);
 }
 
+/* static */
+nsIDocument*
+nsContentUtils::GetSubdocumentWithOuterWindowId(nsIDocument *aDocument,
+                                                uint64_t aOuterWindowId)
+{
+  if (!aDocument || !aOuterWindowId) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetOuterWindowWithId(aOuterWindowId);
+  if (!window) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDocument> foundDoc = window->GetDoc();
+  if (nsContentUtils::ContentIsCrossDocDescendantOf(foundDoc, aDocument)) {
+    // Note that ContentIsCrossDocDescendantOf will return true if
+    // foundDoc == aDocument.
+    return foundDoc;
+  }
+
+  return nullptr;
+}
+
 // Convert the string from the given encoding to Unicode.
 /* static */
 nsresult
 nsContentUtils::ConvertStringFromEncoding(const nsACString& aEncoding,
                                           const nsACString& aInput,
                                           nsAString& aOutput)
 {
   nsAutoCString encoding;
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -320,16 +320,33 @@ public:
    * @param   aDocumentPosition   The document position flags to be reversed.
    *
    * @return  The reversed document position flags.
    *
    * @see nsIDOMNode
    */
   static uint16_t ReverseDocumentPosition(uint16_t aDocumentPosition);
 
+  /**
+   * Returns a subdocument for aDocument with a particular outer window ID.
+   *
+   * @param aDocument
+   *        The document whose subdocuments will be searched.
+   * @param aOuterWindowID
+   *        The outer window ID for the subdocument to be found. This must
+   *        be a value greater than 0.
+   * @return nsIDocument*
+   *        A pointer to the found nsIDocument. nullptr if the subdocument
+   *        cannot be found, or if either aDocument or aOuterWindowId were
+   *        invalid. If the outer window ID belongs to aDocument itself, this
+   *        will return a pointer to aDocument.
+   */
+  static nsIDocument* GetSubdocumentWithOuterWindowId(nsIDocument *aDocument,
+                                                      uint64_t aOuterWindowId);
+
   static uint32_t CopyNewlineNormalizedUnicodeTo(const nsAString& aSource,
                                                  uint32_t aSrcOffset,
                                                  char16_t* aDest,
                                                  uint32_t aLength,
                                                  bool& aLastCharCR);
 
   static uint32_t CopyNewlineNormalizedUnicodeTo(nsReadingIterator<char16_t>& aSrcStart, const nsReadingIterator<char16_t>& aSrcEnd, nsAString& aDest);
 
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2880,23 +2880,36 @@ nsFrameLoader::InitializeBrowserAPI()
     nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
     if (browserFrame) {
       browserFrame->InitializeBrowserAPI();
     }
   }
 }
 
 NS_IMETHODIMP
-nsFrameLoader::StartPersistence(nsIWebBrowserPersistDocumentReceiver* aRecv)
+nsFrameLoader::StartPersistence(uint64_t aOuterWindowID,
+                                nsIWebBrowserPersistDocumentReceiver* aRecv)
 {
+  if (!aRecv) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
   if (mRemoteBrowser) {
-    return mRemoteBrowser->StartPersistence(aRecv);
+    return mRemoteBrowser->StartPersistence(aOuterWindowID, aRecv);
   }
-  if (mDocShell) {
-    nsCOMPtr<nsIDocument> doc = do_GetInterface(mDocShell);
-    NS_ENSURE_STATE(doc);
+
+  nsCOMPtr<nsIDocument> rootDoc = do_GetInterface(mDocShell);
+  nsCOMPtr<nsIDocument> foundDoc;
+  if (aOuterWindowID) {
+    foundDoc = nsContentUtils::GetSubdocumentWithOuterWindowId(rootDoc, aOuterWindowID);
+  } else {
+    foundDoc = rootDoc;
+  }
+
+  if (!foundDoc) {
+    aRecv->OnError(NS_ERROR_NO_CONTENT);
+  } else {
     nsCOMPtr<nsIWebBrowserPersistDocument> pdoc =
-      new mozilla::WebBrowserPersistLocalDocument(doc);
+      new mozilla::WebBrowserPersistLocalDocument(foundDoc);
     aRecv->OnDocumentReady(pdoc);
-    return NS_OK;
   }
-  return NS_ERROR_NO_CONTENT;
+  return NS_OK;
 }
--- a/dom/browser-element/BrowserElementParent.cpp
+++ b/dom/browser-element/BrowserElementParent.cpp
@@ -112,17 +112,17 @@ DispatchCustomDOMEvent(Element* aFrameEl
                          /* cancelable = */ true,
                          aDetailValue,
                          res);
   if (res.Failed()) {
     return false;
   }
   event->SetTrusted(true);
   // Dispatch the event.
-  *aStatus = nsEventStatus_eConsumeNoDefault;
+  // We don't initialize aStatus here, as our callers have already done so.
   nsresult rv =
     EventDispatcher::DispatchDOMEvent(aFrameElement, nullptr,
                                       static_cast<Event*>(event),
                                       presContext, aStatus);
   return NS_SUCCEEDED(rv);
 }
 
 } // namespace
@@ -172,17 +172,17 @@ BrowserElementParent::DispatchOpenWindow
 
   // Do not dispatch a mozbrowseropenwindow event of a widget to its embedder
   nsCOMPtr<nsIMozBrowserFrame> browserFrame =
     do_QueryInterface(aOpenerFrameElement);
   if (browserFrame && browserFrame->GetReallyIsWidget()) {
     return BrowserElementParent::OPEN_WINDOW_CANCELLED;
   }
 
-  nsEventStatus status;
+  nsEventStatus status = nsEventStatus_eIgnore;
   bool dispatchSucceeded =
     DispatchCustomDOMEvent(aOpenerFrameElement,
                            NS_LITERAL_STRING("mozbrowseropenwindow"),
                            cx,
                            val, &status);
 
   if (dispatchSucceeded) {
     if (aPopupFrameElement->IsInDoc()) {
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -116,17 +116,17 @@ both:
                  Principal aPrincipal);
 
     /**
      * Create a layout frame (encapsulating a remote layer tree) for
      * the page that is currently loaded in the <browser>.
      */
     PRenderFrame();
 
-    PWebBrowserPersistDocument();
+    PWebBrowserPersistDocument(uint64_t aOuterWindowID);
 
 parent:
     /**
      * Tell the parent process a new accessible document has been created.
      * aParentDoc is the accessible document it was created in if any, and
      * aParentAcc is the id of the accessible in that document the new document
      * is a child of.
      */
@@ -519,16 +519,17 @@ parent:
                                uint64_t aObserverId);
     SynthesizeNativeTouchTap(IntPoint aPointerScreenPoint,
                              bool aLongTap,
                              uint64_t aObserverId);
     ClearNativeTouchSequence(uint64_t aObserverId);
 child:
     NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
 
+
 parent:
 
     /**
      * Child informs the parent that the graphics objects are ready for
      * compositing.  This is sent when all pending changes have been
      * sent to the compositor and are ready to be shown on the next composite.
      * @see PCompositor
      * @see RequestNotifyAfterRemotePaint
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -3113,26 +3113,38 @@ TabChildGlobal::GetGlobalJSObject()
 {
   NS_ENSURE_TRUE(mTabChild, nullptr);
   nsCOMPtr<nsIXPConnectJSObjectHolder> ref = mTabChild->GetGlobal();
   NS_ENSURE_TRUE(ref, nullptr);
   return ref->GetJSObject();
 }
 
 PWebBrowserPersistDocumentChild*
-TabChild::AllocPWebBrowserPersistDocumentChild()
+TabChild::AllocPWebBrowserPersistDocumentChild(const uint64_t& aOuterWindowID)
 {
   return new WebBrowserPersistDocumentChild();
 }
 
 bool
-TabChild::RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor)
+TabChild::RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor,
+                                                    const uint64_t& aOuterWindowID)
 {
-  nsCOMPtr<nsIDocument> doc = GetDocument();
-  static_cast<WebBrowserPersistDocumentChild*>(aActor)->Start(doc);
+  nsCOMPtr<nsIDocument> rootDoc = GetDocument();
+  nsCOMPtr<nsIDocument> foundDoc;
+  if (aOuterWindowID) {
+    foundDoc = nsContentUtils::GetSubdocumentWithOuterWindowId(rootDoc, aOuterWindowID);
+  } else {
+    foundDoc = rootDoc;
+  }
+
+  if (!foundDoc) {
+    aActor->SendInitFailure(NS_ERROR_NO_CONTENT);
+  } else {
+    static_cast<WebBrowserPersistDocumentChild*>(aActor)->Start(foundDoc);
+  }
   return true;
 }
 
 bool
 TabChild::DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor)
 {
   delete aActor;
   return true;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -490,18 +490,19 @@ public:
     bool ParentIsActive()
     {
       return mParentIsActive;
     }
     bool AsyncPanZoomEnabled() { return mAsyncPanZoomEnabled; }
 
     virtual ScreenIntSize GetInnerSize() override;
 
-    virtual PWebBrowserPersistDocumentChild* AllocPWebBrowserPersistDocumentChild() override;
-    virtual bool RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor) override;
+    virtual PWebBrowserPersistDocumentChild* AllocPWebBrowserPersistDocumentChild(const uint64_t& aOuterWindowID) override;
+    virtual bool RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor,
+                                                           const uint64_t& aOuterWindowID) override;
     virtual bool DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor) override;
 
 protected:
     virtual ~TabChild();
 
     virtual PRenderFrameChild* AllocPRenderFrameChild() override;
     virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
     virtual bool RecvDestroy() override;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3363,34 +3363,35 @@ TabParent::TakeDragVisualization(RefPtr<
 bool
 TabParent::AsyncPanZoomEnabled() const
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   return widget && widget->AsyncPanZoomEnabled();
 }
 
 PWebBrowserPersistDocumentParent*
-TabParent::AllocPWebBrowserPersistDocumentParent()
+TabParent::AllocPWebBrowserPersistDocumentParent(const uint64_t& aOuterWindowID)
 {
   return new WebBrowserPersistDocumentParent();
 }
 
 bool
 TabParent::DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor)
 {
   delete aActor;
   return true;
 }
 
 NS_IMETHODIMP
-TabParent::StartPersistence(nsIWebBrowserPersistDocumentReceiver* aRecv)
+TabParent::StartPersistence(uint64_t aOuterWindowID,
+                            nsIWebBrowserPersistDocumentReceiver* aRecv)
 {
   auto* actor = new WebBrowserPersistDocumentParent();
   actor->SetOnReady(aRecv);
-  return SendPWebBrowserPersistDocumentConstructor(actor)
+  return SendPWebBrowserPersistDocumentConstructor(actor, aOuterWindowID)
     ? NS_OK : NS_ERROR_FAILURE;
   // (The actor will be destroyed on constructor failure.)
 }
 
 NS_IMETHODIMP
 FakeChannel::OnAuthAvailable(nsISupports *aContext, nsIAuthInformation *aAuthInfo)
 {
   nsAuthInformationHolder* holder =
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -430,17 +430,17 @@ public:
                           const int32_t& aDragAreaX, const int32_t& aDragAreaY) override;
 
     void AddInitialDnDDataTo(DataTransfer* aDataTransfer);
 
     void TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                                int32_t& aDragAreaX, int32_t& aDragAreaY);
     layout::RenderFrameParent* GetRenderFrame();
 
-    virtual PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent() override;
+    virtual PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent(const uint64_t& aOuterWindowID) override;
     virtual bool DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor) override;
 
 protected:
     bool ReceiveMessage(const nsString& aMessage,
                         bool aSync,
                         const StructuredCloneData* aCloneData,
                         mozilla::jsipc::CpowHolder* aCpows,
                         nsIPrincipal* aPrincipal,
--- a/dom/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/jsurl/nsJSProtocolHandler.cpp
@@ -560,16 +560,25 @@ nsJSChannel::Open2(nsIInputStream** aStr
     nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
     NS_ENSURE_SUCCESS(rv, rv);
     return Open(aStream);
 }
 
 NS_IMETHODIMP
 nsJSChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
 {
+#ifdef DEBUG
+    {
+    nsCOMPtr<nsILoadInfo> loadInfo = nsIChannel::GetLoadInfo();
+    MOZ_ASSERT(!loadInfo || loadInfo->GetSecurityMode() == 0 ||
+               loadInfo->GetInitialSecurityCheckDone(),
+               "security flags in loadInfo but asyncOpen2() not called");
+    }
+#endif
+
     NS_ENSURE_ARG(aListener);
 
     // First make sure that we have a usable inner window; we'll want to make
     // sure that we execute against that inner and no other.
     nsIScriptGlobalObject* global = GetGlobalObject(this);
     if (!global) {
         return NS_ERROR_NOT_AVAILABLE;
     }
--- a/dom/media/MediaEventSource.h
+++ b/dom/media/MediaEventSource.h
@@ -42,16 +42,25 @@ public:
     return mRevoked;
   }
 
 private:
   ~RevocableToken() {}
   Atomic<bool> mRevoked;
 };
 
+enum class ListenerMode : int8_t {
+  // Allow at most one listener. Move will be used when possible
+  // to pass the event data to save copy.
+  Exclusive,
+  // This is the default. Event data will always be copied when passed
+  // to the listeners.
+  NonExclusive
+};
+
 namespace detail {
 
 /**
  * Define how an event type is passed internally in MediaEventSource and to the
  * listeners. Specialized for the void type to pass a dummy bool instead.
  */
 template <typename T>
 struct EventTypeTraits {
@@ -106,26 +115,26 @@ public:
   explicit RawPtr(T* aPtr) : mPtr(aPtr) {}
   T* get() const { return mPtr; }
 private:
   T* const mPtr;
 };
 
 } // namespace detail
 
-template <typename T> class MediaEventSource;
+template <typename T, ListenerMode> class MediaEventSource;
 
 /**
  * Not thread-safe since this is not meant to be shared and therefore only
  * move constructor is provided. Used to hold the result of
  * MediaEventSource<T>::Connect() and call Disconnect() to disconnect the
  * listener from an event source.
  */
 class MediaEventListener {
-  template <typename T>
+  template <typename T, ListenerMode>
   friend class MediaEventSource;
 
 public:
   MediaEventListener() {}
 
   MediaEventListener(MediaEventListener&& aOther)
     : mToken(Move(aOther.mToken)) {}
 
@@ -149,17 +158,17 @@ private:
   // listeners can be disconnected in a controlled manner.
   explicit MediaEventListener(RevocableToken* aToken) : mToken(aToken) {}
   nsRefPtr<RevocableToken> mToken;
 };
 
 /**
  * A generic and thread-safe class to implement the observer pattern.
  */
-template <typename EventType>
+template <typename EventType, ListenerMode Mode = ListenerMode::NonExclusive>
 class MediaEventSource {
   static_assert(!IsReference<EventType>::value, "Ref-type not supported!");
   typedef typename detail::EventTypeTraits<EventType>::ArgType ArgType;
 
   /**
    * Stored by MediaEventSource to send notifications to the listener.
    */
   class Listener {
@@ -241,16 +250,17 @@ class MediaEventSource {
     const nsRefPtr<Target> mTarget;
     Function mFunction;
   };
 
   template<typename Target, typename Function>
   MediaEventListener
   ConnectInternal(Target* aTarget, const Function& aFunction) {
     MutexAutoLock lock(mMutex);
+    MOZ_ASSERT(Mode == ListenerMode::NonExclusive || mListeners.IsEmpty());
     auto l = mListeners.AppendElement();
     l->reset(new ListenerImpl<Target, Function>(aTarget, aFunction));
     return MediaEventListener((*l)->Token());
   }
 
   // |Method| takes one argument.
   template <typename Target, typename This, typename Method>
   MediaEventListener
@@ -341,18 +351,18 @@ private:
   nsTArray<UniquePtr<Listener>> mListeners;
 };
 
 /**
  * A class to separate the interface of event subject (MediaEventSource)
  * and event publisher. Mostly used as a member variable to publish events
  * to the listeners.
  */
-template <typename EventType>
-class MediaEventProducer : public MediaEventSource<EventType> {
+template <typename EventType, ListenerMode Mode = ListenerMode::NonExclusive>
+class MediaEventProducer : public MediaEventSource<EventType, Mode> {
 public:
   void Notify(const EventType& aEvent) {
     this->NotifyInternal(aEvent);
   }
 };
 
 /**
  * Specialization for void type. A dummy bool is passed to NotifyInternal
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -800,17 +800,16 @@ MediaFormatReader::NotifyDrainComplete(T
 
 void
 MediaFormatReader::NotifyError(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("%s Decoding error", TrackTypeToStr(aTrack));
   auto& decoder = GetDecoderData(aTrack);
   decoder.mError = true;
-  decoder.mNeedDraining = true;
   ScheduleUpdate(aTrack);
 }
 
 void
 MediaFormatReader::NotifyWaitingForData(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
@@ -1139,17 +1138,17 @@ MediaFormatReader::Update(TrackType aTra
       decoder.mDraining = false;
       if (decoder.mError) {
         LOG("Decoding Error");
         decoder.RejectPromise(DECODE_ERROR, __func__);
         return;
       } else if (decoder.mDemuxEOS) {
         decoder.RejectPromise(END_OF_STREAM, __func__);
       }
-    } else if (decoder.mError && !decoder.mDecoder) {
+    } else if (decoder.mError) {
       decoder.RejectPromise(DECODE_ERROR, __func__);
       return;
     } else if (decoder.mWaitingForData) {
       LOG("Waiting For Data");
       decoder.RejectPromise(WAITING_FOR_DATA, __func__);
       return;
     }
   }
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -1003,23 +1003,23 @@ ChannelMediaResource::CacheClientSeek(in
   if (aResume) {
     NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
     // No need to mess with the channel, since we're making a new one
     --mSuspendCount;
   }
 
   mOffset = aOffset;
 
+  // Don't report close of the channel because the channel is not closed for
+  // download ended, but for internal changes in the read position.
+  mIgnoreClose = true;
+
+  // Don't create a new channel if we are still suspended. The channel will
+  // be recreated when we are resumed.
   if (mSuspendCount > 0) {
-    // Close the existing channel to force the channel to be recreated at
-    // the correct offset upon resume.
-    if (mChannel) {
-      mIgnoreClose = true;
-      CloseChannel();
-    }
     return NS_OK;
   }
 
   nsresult rv = RecreateChannel();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return OpenChannel(nullptr);
 }
--- a/dom/media/eme/MediaKeySession.cpp
+++ b/dom/media/eme/MediaKeySession.cpp
@@ -11,17 +11,17 @@
 #include "mozilla/dom/MediaEncryptedEvent.h"
 #include "mozilla/dom/MediaKeyStatusMap.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/CDMProxy.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/Move.h"
 #include "nsContentUtils.h"
 #include "mozilla/EMEUtils.h"
-#include "mozilla/Base64.h"
+#include "GMPUtils.h"
 #include "nsPrintfCString.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaKeySession,
                                    DOMEventTargetHelper,
                                    mMediaKeyError,
@@ -138,24 +138,17 @@ MediaKeySession::UpdateKeyStatusMap()
 
   mKeyStatusMap->Update(keyStatuses);
 
   if (EME_LOG_ENABLED()) {
     nsAutoCString message(
       nsPrintfCString("MediaKeySession[%p,'%s'] key statuses change {",
                       this, NS_ConvertUTF16toUTF8(mSessionId).get()));
     for (const CDMCaps::KeyStatus& status : keyStatuses) {
-      nsAutoCString base64KeyId;
-      nsDependentCSubstring rawKeyId(reinterpret_cast<const char*>(status.mId.Elements()),
-                                     status.mId.Length());
-      nsresult rv = Base64Encode(rawKeyId, base64KeyId);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        continue;
-      }
-      message.Append(nsPrintfCString(" (%s,%s)", base64KeyId.get(),
+      message.Append(nsPrintfCString(" (%s,%s)", ToBase64(status.mId).get(),
         MediaKeyStatusValues::strings[status.mStatus].value));
     }
     message.Append(" }");
     EME_LOG(message.get());
   }
 }
 
 MediaKeyStatusMap*
@@ -192,27 +185,19 @@ MediaKeySession::GenerateRequest(const n
                          NS_LITERAL_CSTRING("Bad arguments to MediaKeySession.generateRequest()"));
     EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, "
             "invalid initData or initDataType",
       this, NS_ConvertUTF16toUTF8(mSessionId).get());
     return promise.forget();
   }
 
   // Convert initData to base64 for easier logging.
-  // Note: UpdateSession() Move()s the data out of the array, so we have
+  // Note: CreateSession() Move()s the data out of the array, so we have
   // to copy it here.
-  nsAutoCString base64InitData;
-  if (EME_LOG_ENABLED()) {
-    nsDependentCSubstring rawInitData(reinterpret_cast<const char*>(data.Elements()),
-      data.Length());
-    if (NS_FAILED(Base64Encode(rawInitData, base64InitData))) {
-      NS_WARNING("Failed to base64 encode initData for logging");
-    }
-  }
-
+  nsAutoCString base64InitData(ToBase64(data));
   PromiseId pid = mKeys->StorePromise(promise);
   mKeys->GetCDMProxy()->CreateSession(Token(),
                                       mSessionType,
                                       pid,
                                       aInitDataType, data);
 
   EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() sent, "
           "promiseId=%d initData(base64)='%s'",
@@ -291,24 +276,17 @@ MediaKeySession::Update(const ArrayBuffe
             this, NS_ConvertUTF16toUTF8(mSessionId).get());
     return promise.forget();
   }
 
 
   // Convert response to base64 for easier logging.
   // Note: UpdateSession() Move()s the data out of the array, so we have
   // to copy it here.
-  nsAutoCString base64Response;
-  if (EME_LOG_ENABLED()) {
-    nsDependentCSubstring rawResponse(reinterpret_cast<const char*>(data.Elements()),
-      data.Length());
-    if (NS_FAILED(Base64Encode(rawResponse, base64Response))) {
-      NS_WARNING("Failed to base64 encode response for logging");
-    }
-  }
+  nsAutoCString base64Response(ToBase64(data));
 
   PromiseId pid = mKeys->StorePromise(promise);
   mKeys->GetCDMProxy()->UpdateSession(mSessionId,
                                       pid,
                                       data);
 
   EME_LOG("MediaKeySession[%p,'%s'] Update() sent to CDM, "
           "promiseId=%d Response(base64)='%s'",
@@ -395,26 +373,20 @@ MediaKeySession::Remove(ErrorResult& aRv
   return promise.forget();
 }
 
 void
 MediaKeySession::DispatchKeyMessage(MediaKeyMessageType aMessageType,
                                     const nsTArray<uint8_t>& aMessage)
 {
   if (EME_LOG_ENABLED()) {
-    nsAutoCString base64MsgData;
-    nsDependentCSubstring rawMsgData(reinterpret_cast<const char*>(aMessage.Elements()),
-                                     aMessage.Length());
-    if (NS_FAILED(Base64Encode(rawMsgData, base64MsgData))) {
-      NS_WARNING("Failed to base64 encode message for logging");
-    }
     EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyMessage() type=%s message(base64)='%s'",
             this, NS_ConvertUTF16toUTF8(mSessionId).get(),
             MediaKeyMessageTypeValues::strings[uint32_t(aMessageType)].value,
-            base64MsgData.get());
+            ToBase64(aMessage).get());
   }
 
   nsRefPtr<MediaKeyMessageEvent> event(
     MediaKeyMessageEvent::Constructor(this, aMessageType, aMessage));
   nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
     new AsyncEventDispatcher(this, event);
   asyncDispatcher->PostDOMEvent();
 }
--- a/dom/media/gmp-plugin/gmp-test-output-protection.h
+++ b/dom/media/gmp-plugin/gmp-test-output-protection.h
@@ -30,38 +30,38 @@ static BOOL CALLBACK EnumDisplayMonitors
 
   MONITORINFOEXA miex;
   ZeroMemory(&miex, sizeof(miex));
   miex.cbSize = sizeof(miex);
   if (!GetMonitorInfoA(hMonitor, &miex)) {
     failureMsgs->push_back("FAIL GetMonitorInfoA call failed");
   }
 
-  DISPLAY_DEVICEA dd;
-  ZeroMemory(&dd, sizeof(dd));
-  dd.cb = sizeof(dd);
-  if (!EnumDisplayDevicesA(miex.szDevice, 0, &dd, 1)) {
-    failureMsgs->push_back("FAIL EnumDisplayDevicesA call failed");
-  }
-
   ULONG numVideoOutputs = 0;
   IOPMVideoOutput** opmVideoOutputArray = nullptr;
   HRESULT hr = sOPMGetVideoOutputsFromHMONITORProc(hMonitor,
                                                    OPM_VOS_OPM_SEMANTICS,
                                                    &numVideoOutputs,
                                                    &opmVideoOutputArray);
   if (S_OK != hr) {
     if (0x8007001f != hr && 0x80070032 != hr && 0xc02625e5 != hr) {
       char msg[100];
       sprintf(msg, "FAIL OPMGetVideoOutputsFromHMONITOR call failed: HRESULT=0x%08x", hr);
       failureMsgs->push_back(msg);
     }
     return true;
   }
 
+  DISPLAY_DEVICEA dd;
+  ZeroMemory(&dd, sizeof(dd));
+  dd.cb = sizeof(dd);
+  if (!EnumDisplayDevicesA(miex.szDevice, 0, &dd, 1)) {
+    failureMsgs->push_back("FAIL EnumDisplayDevicesA call failed");
+  }
+
   for (ULONG i = 0; i < numVideoOutputs; ++i) {
     OPM_RANDOM_NUMBER opmRandomNumber;
     BYTE* certificate = nullptr;
     ULONG certificateLength = 0;
     hr = opmVideoOutputArray[i]->StartInitialization(&opmRandomNumber,
                                                      &certificate,
                                                      &certificateLength);
     if (S_OK != hr) {
--- a/dom/media/gmp/GMPAudioDecoderParent.cpp
+++ b/dom/media/gmp/GMPAudioDecoderParent.cpp
@@ -14,16 +14,17 @@
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 extern PRLogModuleInfo* GetGMPLog();
 
+#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
 #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
 #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
 
 namespace gmp {
 
 GMPAudioDecoderParent::GMPAudioDecoderParent(GMPContentParent* aPlugin)
   : mIsOpen(false)
   , mShuttingDown(false)
@@ -43,16 +44,18 @@ GMPAudioDecoderParent::~GMPAudioDecoderP
 nsresult
 GMPAudioDecoderParent::InitDecode(GMPAudioCodecType aCodecType,
                                   uint32_t aChannelCount,
                                   uint32_t aBitsPerChannel,
                                   uint32_t aSamplesPerSecond,
                                   nsTArray<uint8_t>& aExtraData,
                                   GMPAudioDecoderCallbackProxy* aCallback)
 {
+  LOGD(("GMPAudioDecoderParent[%p]::InitDecode()", this));
+
   if (mIsOpen) {
     NS_WARNING("Trying to re-init an in-use GMP audio decoder!");
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
 
   if (!aCallback) {
@@ -73,16 +76,18 @@ GMPAudioDecoderParent::InitDecode(GMPAud
 
   // Async IPC, we don't have access to a return value.
   return NS_OK;
 }
 
 nsresult
 GMPAudioDecoderParent::Decode(GMPAudioSamplesImpl& aEncodedSamples)
 {
+  LOGV(("GMPAudioDecoderParent[%p]::Decode() timestamp=%lld",
+        this, aEncodedSamples.TimeStamp()));
 
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP Audio decoder!");
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
 
@@ -95,16 +100,18 @@ GMPAudioDecoderParent::Decode(GMPAudioSa
 
   // Async IPC, we don't have access to a return value.
   return NS_OK;
 }
 
 nsresult
 GMPAudioDecoderParent::Reset()
 {
+  LOGD(("GMPAudioDecoderParent[%p]::Reset()", this));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP Audio decoder!");
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
 
   if (!SendReset()) {
@@ -115,16 +122,18 @@ GMPAudioDecoderParent::Reset()
 
   // Async IPC, we don't have access to a return value.
   return NS_OK;
 }
 
 nsresult
 GMPAudioDecoderParent::Drain()
 {
+  LOGD(("GMPAudioDecoderParent[%p]::Drain()", this));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP Audio decoder!");
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
 
   if (!SendDrain()) {
@@ -136,17 +145,17 @@ GMPAudioDecoderParent::Drain()
   // Async IPC, we don't have access to a return value.
   return NS_OK;
 }
 
 // Note: Consider keeping ActorDestroy sync'd up when making changes here.
 nsresult
 GMPAudioDecoderParent::Close()
 {
-  LOGD(("%s: %p", __FUNCTION__, this));
+  LOGD(("GMPAudioDecoderParent[%p]::Close()", this));
   MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
 
   // Ensure if we've received a Close while waiting for a ResetComplete
   // or DrainComplete notification, we'll unblock the caller before processing
   // the close. This seems unlikely to happen, but better to be careful.
   UnblockResetAndDrain();
 
   // Consumer is done with us; we can shut down.  No more callbacks should
@@ -161,17 +170,17 @@ GMPAudioDecoderParent::Close()
 
   return NS_OK;
 }
 
 // Note: Consider keeping ActorDestroy sync'd up when making changes here.
 nsresult
 GMPAudioDecoderParent::Shutdown()
 {
-  LOGD(("%s: %p", __FUNCTION__, this));
+  LOGD(("GMPAudioDecoderParent[%p]::Shutdown()", this));
   MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
 
   if (mShuttingDown) {
     return NS_OK;
   }
   mShuttingDown = true;
 
   // Ensure if we've received a shutdown while waiting for a ResetComplete
@@ -192,16 +201,18 @@ GMPAudioDecoderParent::Shutdown()
 
   return NS_OK;
 }
 
 // Note: Keep this sync'd up with DecodingComplete
 void
 GMPAudioDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
 {
+  LOGD(("GMPAudioDecoderParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
+
   mIsOpen = false;
   mActorDestroyed = true;
 
   // Ensure if we've received a destroy while waiting for a ResetComplete
   // or DrainComplete notification, we'll unblock the caller before processing
   // the error.
   UnblockResetAndDrain();
 
@@ -215,44 +226,51 @@ GMPAudioDecoderParent::ActorDestroy(Acto
     mPlugin->AudioDecoderDestroyed(this);
     mPlugin = nullptr;
   }
 }
 
 bool
 GMPAudioDecoderParent::RecvDecoded(const GMPAudioDecodedSampleData& aDecoded)
 {
+  LOGV(("GMPAudioDecoderParent[%p]::RecvDecoded() timestamp=%lld",
+        this, aDecoded.mTimeStamp()));
+
   if (!mCallback) {
     return false;
   }
 
   mCallback->Decoded(aDecoded.mData(),
                      aDecoded.mTimeStamp(),
                      aDecoded.mChannelCount(),
                      aDecoded.mSamplesPerSecond());
 
   return true;
 }
 
 bool
 GMPAudioDecoderParent::RecvInputDataExhausted()
 {
+  LOGV(("GMPAudioDecoderParent[%p]::RecvInputDataExhausted()", this));
+
   if (!mCallback) {
     return false;
   }
 
   // Ignore any return code. It is OK for this to fail without killing the process.
   mCallback->InputDataExhausted();
 
   return true;
 }
 
 bool
 GMPAudioDecoderParent::RecvDrainComplete()
 {
+  LOGD(("GMPAudioDecoderParent[%p]::RecvDrainComplete()", this));
+
   if (!mCallback) {
     return false;
   }
 
   if (!mIsAwaitingDrainComplete) {
     return true;
   }
   mIsAwaitingDrainComplete = false;
@@ -261,16 +279,18 @@ GMPAudioDecoderParent::RecvDrainComplete
   mCallback->DrainComplete();
 
   return true;
 }
 
 bool
 GMPAudioDecoderParent::RecvResetComplete()
 {
+  LOGD(("GMPAudioDecoderParent[%p]::RecvResetComplete()", this));
+
   if (!mCallback) {
     return false;
   }
 
   if (!mIsAwaitingResetComplete) {
     return true;
   }
   mIsAwaitingResetComplete = false;
@@ -279,16 +299,18 @@ GMPAudioDecoderParent::RecvResetComplete
   mCallback->ResetComplete();
 
   return true;
 }
 
 bool
 GMPAudioDecoderParent::RecvError(const GMPErr& aError)
 {
+  LOGD(("GMPAudioDecoderParent[%p]::RecvError(error=%d)", this, aError));
+
   if (!mCallback) {
     return false;
   }
 
   // Ensure if we've received an error while waiting for a ResetComplete
   // or DrainComplete notification, we'll unblock the caller before processing
   // the error.
   UnblockResetAndDrain();
@@ -297,35 +319,41 @@ GMPAudioDecoderParent::RecvError(const G
   mCallback->Error(aError);
 
   return true;
 }
 
 bool
 GMPAudioDecoderParent::RecvShutdown()
 {
+  LOGD(("GMPAudioDecoderParent[%p]::RecvShutdown()", this));
+
   Shutdown();
   return true;
 }
 
 bool
 GMPAudioDecoderParent::Recv__delete__()
 {
+  LOGD(("GMPAudioDecoderParent[%p]::Recv__delete__()", this));
+
   if (mPlugin) {
     // Ignore any return code. It is OK for this to fail without killing the process.
     mPlugin->AudioDecoderDestroyed(this);
     mPlugin = nullptr;
   }
 
   return true;
 }
 
 void
 GMPAudioDecoderParent::UnblockResetAndDrain()
 {
+  LOGD(("GMPAudioDecoderParent[%p]::UnblockResetAndDrain()", this));
+
   if (!mCallback) {
     MOZ_ASSERT(!mIsAwaitingResetComplete);
     MOZ_ASSERT(!mIsAwaitingDrainComplete);
     return;
   }
   if (mIsAwaitingResetComplete) {
     mIsAwaitingResetComplete = false;
     mCallback->ResetComplete();
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -4,16 +4,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GMPDecryptorParent.h"
 #include "GMPContentParent.h"
 #include "MediaData.h"
 #include "mozilla/unused.h"
 
 namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern PRLogModuleInfo* GetGMPLog();
+
+#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
 namespace gmp {
 
 GMPDecryptorParent::GMPDecryptorParent(GMPContentParent* aPlugin)
   : mIsOpen(false)
   , mShuttingDown(false)
   , mActorDestroyed(false)
   , mPlugin(aPlugin)
   , mPluginId(aPlugin->GetPluginId())
@@ -27,16 +38,18 @@ GMPDecryptorParent::GMPDecryptorParent(G
 
 GMPDecryptorParent::~GMPDecryptorParent()
 {
 }
 
 nsresult
 GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback)
 {
+  LOGD(("GMPDecryptorParent[%p]::Init()", this));
+
   if (mIsOpen) {
     NS_WARNING("Trying to re-use an in-use GMP decrypter!");
     return NS_ERROR_FAILURE;
   }
   mCallback = aCallback;
   if (!SendInit()) {
     return NS_ERROR_FAILURE;
   }
@@ -46,96 +59,115 @@ GMPDecryptorParent::Init(GMPDecryptorPro
 
 void
 GMPDecryptorParent::CreateSession(uint32_t aCreateSessionToken,
                                   uint32_t aPromiseId,
                                   const nsCString& aInitDataType,
                                   const nsTArray<uint8_t>& aInitData,
                                   GMPSessionType aSessionType)
 {
+  LOGD(("GMPDecryptorParent[%p]::CreateSession(token=%u, promiseId=%u, aInitData='%s')",
+        this, aCreateSessionToken, aPromiseId, ToBase64(aInitData).get()));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return;
   }
   // Caller should ensure parameters passed in from JS are valid.
   MOZ_ASSERT(!aInitDataType.IsEmpty() && !aInitData.IsEmpty());
   unused << SendCreateSession(aCreateSessionToken, aPromiseId, aInitDataType, aInitData, aSessionType);
 }
 
 void
 GMPDecryptorParent::LoadSession(uint32_t aPromiseId,
                                 const nsCString& aSessionId)
 {
+  LOGD(("GMPDecryptorParent[%p]::LoadSession(sessionId='%s', promiseId=%u)",
+        this, aSessionId.get(), aPromiseId));
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return;
   }
   // Caller should ensure parameters passed in from JS are valid.
   MOZ_ASSERT(!aSessionId.IsEmpty());
   unused << SendLoadSession(aPromiseId, aSessionId);
 }
 
 void
 GMPDecryptorParent::UpdateSession(uint32_t aPromiseId,
                                   const nsCString& aSessionId,
                                   const nsTArray<uint8_t>& aResponse)
 {
+  LOGD(("GMPDecryptorParent[%p]::UpdateSession(sessionId='%s', promiseId=%u response='%s')",
+        this, aSessionId.get(), aPromiseId, ToBase64(aResponse).get()));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return;
   }
   // Caller should ensure parameters passed in from JS are valid.
   MOZ_ASSERT(!aSessionId.IsEmpty() && !aResponse.IsEmpty());
   unused << SendUpdateSession(aPromiseId, aSessionId, aResponse);
 }
 
 void
 GMPDecryptorParent::CloseSession(uint32_t aPromiseId,
                                  const nsCString& aSessionId)
 {
+  LOGD(("GMPDecryptorParent[%p]::CloseSession(sessionId='%s', promiseId=%u)",
+         this, aSessionId.get(), aPromiseId));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return;
   }
   // Caller should ensure parameters passed in from JS are valid.
   MOZ_ASSERT(!aSessionId.IsEmpty());
   unused << SendCloseSession(aPromiseId, aSessionId);
 }
 
 void
 GMPDecryptorParent::RemoveSession(uint32_t aPromiseId,
                                   const nsCString& aSessionId)
 {
+  LOGD(("GMPDecryptorParent[%p]::RemoveSession(sessionId='%s', promiseId=%u)",
+        this, aSessionId.get(), aPromiseId));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return;
   }
   // Caller should ensure parameters passed in from JS are valid.
   MOZ_ASSERT(!aSessionId.IsEmpty());
   unused << SendRemoveSession(aPromiseId, aSessionId);
 }
 
 void
 GMPDecryptorParent::SetServerCertificate(uint32_t aPromiseId,
                                          const nsTArray<uint8_t>& aServerCert)
 {
+  LOGD(("GMPDecryptorParent[%p]::SetServerCertificate(promiseId=%u)",
+        this, aPromiseId));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return;
   }
   // Caller should ensure parameters passed in from JS are valid.
   MOZ_ASSERT(!aServerCert.IsEmpty());
   unused << SendSetServerCertificate(aPromiseId, aServerCert);
 }
 
 void
 GMPDecryptorParent::Decrypt(uint32_t aId,
                             const CryptoSample& aCrypto,
                             const nsTArray<uint8_t>& aBuffer)
 {
+  LOGV(("GMPDecryptorParent[%p]::Decrypt(id=%d)", this, aId));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return;
   }
 
   // Caller should ensure parameters passed in are valid.
   MOZ_ASSERT(!aBuffer.IsEmpty() && aCrypto.mValid);
 
@@ -147,39 +179,48 @@ GMPDecryptorParent::Decrypt(uint32_t aId
 
   unused << SendDecrypt(aId, aBuffer, data);
 }
 
 bool
 GMPDecryptorParent::RecvSetSessionId(const uint32_t& aCreateSessionId,
                                      const nsCString& aSessionId)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvSetSessionId(token=%u, sessionId='%s')",
+        this, aCreateSessionId, aSessionId.get()));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->SetSessionId(aCreateSessionId, aSessionId);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
                                                   const bool& aSuccess)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvResolveLoadSessionPromise(promiseId=%u)",
+        this, aPromiseId));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->ResolveLoadSessionPromise(aPromiseId, aSuccess);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvResolvePromise(const uint32_t& aPromiseId)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvResolvePromise(promiseId=%u)",
+        this, aPromiseId));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->ResolvePromise(aPromiseId);
   return true;
 }
 
@@ -201,138 +242,167 @@ GMPExToNsresult(GMPDOMException aDomExce
   }
 }
 
 bool
 GMPDecryptorParent::RecvRejectPromise(const uint32_t& aPromiseId,
                                       const GMPDOMException& aException,
                                       const nsCString& aMessage)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvRejectPromise(promiseId=%u, exception=%d, msg='%s')",
+        this, aException, aMessage.get()));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->RejectPromise(aPromiseId, GMPExToNsresult(aException), aMessage);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvSessionMessage(const nsCString& aSessionId,
                                        const GMPSessionMessageType& aMessageType,
                                        nsTArray<uint8_t>&& aMessage)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvSessionMessage(sessionId='%s', type=%d, msg='%s')",
+        this, aSessionId.get(), aMessageType, ToBase64(aMessage).get()));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->SessionMessage(aSessionId, aMessageType, aMessage);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvExpirationChange(const nsCString& aSessionId,
                                          const double& aExpiryTime)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvExpirationChange(sessionId='%s', expiry=%lf)",
+        this, aSessionId.get(), aExpiryTime));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->ExpirationChange(aSessionId, aExpiryTime);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvSessionClosed(const nsCString& aSessionId)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvSessionClosed(sessionId='%s')",
+        this, aSessionId.get()));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->SessionClosed(aSessionId);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId,
                                      const GMPDOMException& aException,
                                      const uint32_t& aSystemCode,
                                      const nsCString& aMessage)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvSessionError(sessionId='%s', exception=%d, sysCode=%d, msg='%s')",
+        this, aSessionId.get(),
+        aException, aSystemCode, aMessage.get()));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->SessionError(aSessionId,
                           GMPExToNsresult(aException),
                           aSystemCode,
                           aMessage);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvKeyStatusChanged(const nsCString& aSessionId,
                                          InfallibleTArray<uint8_t>&& aKeyId,
                                          const GMPMediaKeyStatus& aStatus)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvKeyStatusChanged(sessionId='%s', keyId=%s, status=%d)",
+        this, aSessionId.get(), ToBase64(aKeyId).get(), aStatus));
+
   if (mIsOpen) {
     mCallback->KeyStatusChanged(aSessionId, aKeyId, aStatus);
   }
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvSetCaps(const uint64_t& aCaps)
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvSetCaps(caps=0x%llx)", this, aCaps));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->SetCaps(aCaps);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvDecrypted(const uint32_t& aId,
                                   const GMPErr& aErr,
                                   InfallibleTArray<uint8_t>&& aBuffer)
 {
+  LOGV(("GMPDecryptorParent[%p]::RecvDecrypted(id=%d, err=%d)",
+        this, aId, aErr));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->Decrypted(aId, aErr, aBuffer);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvShutdown()
 {
+  LOGD(("GMPDecryptorParent[%p]::RecvShutdown()", this));
+
   Shutdown();
   return true;
 }
 
 // Note: may be called via Terminated()
 void
 GMPDecryptorParent::Close()
 {
+  LOGD(("GMPDecryptorParent[%p]::Close()", this));
   MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
   // Consumer is done with us; we can shut down.  No more callbacks should
   // be made to mCallback. Note: do this before Shutdown()!
   mCallback = nullptr;
   // Let Shutdown mark us as dead so it knows if we had been alive
 
   // In case this is the last reference
   nsRefPtr<GMPDecryptorParent> kungfudeathgrip(this);
   this->Release();
   Shutdown();
 }
 
 void
 GMPDecryptorParent::Shutdown()
 {
+  LOGD(("GMPDecryptorParent[%p]::Shutdown()", this));
   MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
 
   if (mShuttingDown) {
     return;
   }
   mShuttingDown = true;
 
   // Notify client we're gone!  Won't occur after Close()
@@ -346,32 +416,36 @@ GMPDecryptorParent::Shutdown()
     unused << SendDecryptingComplete();
   }
 }
 
 // Note: Keep this sync'd up with Shutdown
 void
 GMPDecryptorParent::ActorDestroy(ActorDestroyReason aWhy)
 {
+  LOGD(("GMPDecryptorParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
+
   mIsOpen = false;
   mActorDestroyed = true;
   if (mCallback) {
     // May call Close() (and Shutdown()) immediately or with a delay
     mCallback->Terminated();
     mCallback = nullptr;
   }
   if (mPlugin) {
     mPlugin->DecryptorDestroyed(this);
     mPlugin = nullptr;
   }
 }
 
 bool
 GMPDecryptorParent::Recv__delete__()
 {
+  LOGD(("GMPDecryptorParent[%p]::Recv__delete__()", this));
+
   if (mPlugin) {
     mPlugin->DecryptorDestroyed(this);
     mPlugin = nullptr;
   }
   return true;
 }
 
 } // namespace gmp
--- a/dom/media/gmp/GMPStorageParent.cpp
+++ b/dom/media/gmp/GMPStorageParent.cpp
@@ -25,21 +25,16 @@ namespace mozilla {
 #undef LOG
 #endif
 
 extern PRLogModuleInfo* GetGMPLog();
 
 #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
 #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
 
-#ifdef __CLASS__
-#undef __CLASS__
-#endif
-#define __CLASS__ "GMPStorageParent"
-
 namespace gmp {
 
 // We store the records in files in the profile dir.
 // $profileDir/gmp/storage/$nodeId/
 static nsresult
 GetGMPStorageDir(nsIFile** aTempDir, const nsCString& aNodeId)
 {
   if (NS_WARN_IF(!aTempDir)) {
@@ -160,17 +155,17 @@ public:
 
     return NS_OK;
   }
 
   GMPErr Open(const nsCString& aRecordName) override
   {
     MOZ_ASSERT(!IsOpen(aRecordName));
     nsresult rv;
-    Record* record = nullptr; 
+    Record* record = nullptr;
     if (!mRecords.Get(aRecordName, &record)) {
       // New file.
       nsAutoString filename;
       rv = GetUnusedFilename(aRecordName, filename);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return GMPGenericErr;
       }
       record = new Record(filename, aRecordName);
@@ -249,17 +244,17 @@ public:
 
   GMPErr Write(const nsCString& aRecordName,
                const nsTArray<uint8_t>& aBytes) override
   {
     if (!IsOpen(aRecordName)) {
       return GMPClosedErr;
     }
 
-    Record* record = nullptr; 
+    Record* record = nullptr;
     mRecords.Get(aRecordName, &record);
     MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this.
 
     // Write operations overwrite the entire record. So close it now.
     PR_Close(record->mFileDesc);
     record->mFileDesc = nullptr;
 
     // Writing 0 bytes means removing (deleting) the file.
@@ -576,16 +571,18 @@ GMPStorageParent::GMPStorageParent(const
   , mPlugin(aPlugin)
   , mShutdown(false)
 {
 }
 
 nsresult
 GMPStorageParent::Init()
 {
+  LOGD(("GMPStorageParent[%p]::Init()", this));
+
   if (NS_WARN_IF(mNodeId.IsEmpty())) {
     return NS_ERROR_FAILURE;
   }
   nsCOMPtr<mozIGeckoMediaPluginChromeService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (NS_WARN_IF(!mps)) {
     return NS_ERROR_FAILURE;
   }
@@ -606,130 +603,158 @@ GMPStorageParent::Init()
   }
 
   return NS_OK;
 }
 
 bool
 GMPStorageParent::RecvOpen(const nsCString& aRecordName)
 {
+  LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s')",
+       this, aRecordName.get()));
+
   if (mShutdown) {
     return false;
   }
 
   if (mNodeId.EqualsLiteral("null")) {
     // Refuse to open storage if the page is opened from local disk,
     // or shared across origin.
-    NS_WARNING("Refusing to open storage for null NodeId");
+    LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; null nodeId",
+          this, aRecordName.get()));
     unused << SendOpenComplete(aRecordName, GMPGenericErr);
     return true;
   }
 
   if (aRecordName.IsEmpty()) {
+    LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record name empty",
+          this, aRecordName.get()));
     unused << SendOpenComplete(aRecordName, GMPGenericErr);
     return true;
   }
 
   if (mStorage->IsOpen(aRecordName)) {
+    LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record in use",
+          this, aRecordName.get()));
     unused << SendOpenComplete(aRecordName, GMPRecordInUse);
     return true;
   }
 
   auto err = mStorage->Open(aRecordName);
   MOZ_ASSERT(GMP_FAILED(err) || mStorage->IsOpen(aRecordName));
+  LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') complete; rv=%d",
+        this, aRecordName.get(), err));
   unused << SendOpenComplete(aRecordName, err);
 
   return true;
 }
 
 bool
 GMPStorageParent::RecvRead(const nsCString& aRecordName)
 {
-  LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
+  LOGD(("GMPStorageParent[%p]::RecvRead(record='%s')",
+       this, aRecordName.get()));
 
   if (mShutdown) {
     return false;
   }
 
   nsTArray<uint8_t> data;
   if (!mStorage->IsOpen(aRecordName)) {
+    LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') failed; record not open",
+         this, aRecordName.get()));
     unused << SendReadComplete(aRecordName, GMPClosedErr, data);
   } else {
-    unused << SendReadComplete(aRecordName, mStorage->Read(aRecordName, data), data);
+    GMPErr rv = mStorage->Read(aRecordName, data);
+    LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') read %d bytes rv=%d",
+      this, aRecordName.get(), data.Length(), rv));
+    unused << SendReadComplete(aRecordName, rv, data);
   }
 
   return true;
 }
 
 bool
 GMPStorageParent::RecvWrite(const nsCString& aRecordName,
                             InfallibleTArray<uint8_t>&& aBytes)
 {
-  LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
+  LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') %d bytes",
+        this, aRecordName.get(), aBytes.Length()));
 
   if (mShutdown) {
     return false;
   }
 
   if (!mStorage->IsOpen(aRecordName)) {
+    LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record not open",
+          this, aRecordName.get()));
     unused << SendWriteComplete(aRecordName, GMPClosedErr);
     return true;
   }
 
   if (aBytes.Length() > GMP_MAX_RECORD_SIZE) {
+    LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record too big",
+          this, aRecordName.get()));
     unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr);
     return true;
   }
 
-  unused << SendWriteComplete(aRecordName, mStorage->Write(aRecordName, aBytes));
+  GMPErr rv = mStorage->Write(aRecordName, aBytes);
+  LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') write complete rv=%d",
+        this, aRecordName.get(), rv));
+
+  unused << SendWriteComplete(aRecordName, rv);
 
   return true;
 }
 
 bool
 GMPStorageParent::RecvGetRecordNames()
 {
-  LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
-
   if (mShutdown) {
     return true;
   }
 
   nsTArray<nsCString> recordNames;
   GMPErr status = mStorage->GetRecordNames(recordNames);
+
+  LOGD(("GMPStorageParent[%p]::RecvGetRecordNames() status=%d numRecords=%d",
+        this, status, recordNames.Length()));
+
   unused << SendRecordNames(recordNames, status);
 
   return true;
 }
 
 bool
 GMPStorageParent::RecvClose(const nsCString& aRecordName)
 {
-  LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
+  LOGD(("GMPStorageParent[%p]::RecvClose(record='%s')",
+        this, aRecordName.get()));
 
   if (mShutdown) {
     return true;
   }
 
   mStorage->Close(aRecordName);
 
   return true;
 }
 
 void
 GMPStorageParent::ActorDestroy(ActorDestroyReason aWhy)
 {
-  LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+  LOGD(("GMPStorageParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
   Shutdown();
 }
 
 void
 GMPStorageParent::Shutdown()
 {
-  LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+  LOGD(("GMPStorageParent[%p]::Shutdown()", this));
 
   if (mShutdown) {
     return;
   }
   mShutdown = true;
   unused << SendShutdown();
 
   mStorage = nullptr;
--- a/dom/media/gmp/GMPUtils.cpp
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -5,16 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GMPUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsCOMPtr.h"
 #include "nsLiteralString.h"
 #include "nsCRTGlue.h"
+#include "mozilla/Base64.h"
 
 namespace mozilla {
 
 bool
 GetEMEVoucherPath(nsIFile** aPath)
 {
   nsCOMPtr<nsIFile> path;
   NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(path));
@@ -45,9 +46,22 @@ SplitAt(const char* aDelims,
   nsAutoCString str(aInput);
   char* end = str.BeginWriting();
   const char* start = nullptr;
   while (!!(start = NS_strtok(aDelims, &end))) {
     aOutTokens.AppendElement(nsCString(start));
   }
 }
 
+nsCString
+ToBase64(const nsTArray<uint8_t>& aBytes)
+{
+  nsAutoCString base64;
+  nsDependentCSubstring raw(reinterpret_cast<const char*>(aBytes.Elements()),
+                            aBytes.Length());
+  nsresult rv = Base64Encode(raw, base64);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_LITERAL_CSTRING("[Base64EncodeFailed]");
+  }
+  return base64;
+}
+
 } // namespace mozilla
--- a/dom/media/gmp/GMPUtils.h
+++ b/dom/media/gmp/GMPUtils.h
@@ -29,11 +29,14 @@ bool GetEMEVoucherPath(nsIFile** aPath);
 
 bool EMEVoucherFileExists();
 
 void
 SplitAt(const char* aDelims,
         const nsACString& aInput,
         nsTArray<nsCString>& aOutTokens);
 
+nsCString
+ToBase64(const nsTArray<uint8_t>& aBytes);
+
 } // namespace mozilla
 
 #endif
--- a/dom/media/gmp/GMPVideoDecoderParent.cpp
+++ b/dom/media/gmp/GMPVideoDecoderParent.cpp
@@ -18,16 +18,17 @@
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 extern PRLogModuleInfo* GetGMPLog();
 
+#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
 #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
 #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
 
 namespace gmp {
 
 // States:
 // Initial: mIsOpen == false
 //    on InitDecode success -> Open
@@ -62,17 +63,17 @@ GMPVideoDecoderParent::Host()
 {
   return mVideoHost;
 }
 
 // Note: may be called via Terminated()
 void
 GMPVideoDecoderParent::Close()
 {
-  LOGD(("%s: %p", __FUNCTION__, this));
+  LOGD(("GMPVideoDecoderParent[%p]::Close()", this));
   MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
 
   // Ensure if we've received a Close while waiting for a ResetComplete
   // or DrainComplete notification, we'll unblock the caller before processing
   // the close. This seems unlikely to happen, but better to be careful.
   UnblockResetAndDrain();
 
   // Consumer is done with us; we can shut down.  No more callbacks should
@@ -87,16 +88,18 @@ GMPVideoDecoderParent::Close()
 }
 
 nsresult
 GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings,
                                   const nsTArray<uint8_t>& aCodecSpecific,
                                   GMPVideoDecoderCallbackProxy* aCallback,
                                   int32_t aCoreCount)
 {
+  LOGD(("GMPVideoDecoderParent[%p]::InitDecode()", this));
+
   if (mActorDestroyed) {
     NS_WARNING("Trying to use a destroyed GMP video decoder!");
     return NS_ERROR_FAILURE;
   }
   if (mIsOpen) {
     NS_WARNING("Trying to re-init an in-use GMP video decoder!");
     return NS_ERROR_FAILURE;
   }
@@ -118,16 +121,20 @@ GMPVideoDecoderParent::InitDecode(const 
 }
 
 nsresult
 GMPVideoDecoderParent::Decode(GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame,
                               bool aMissingFrames,
                               const nsTArray<uint8_t>& aCodecSpecificInfo,
                               int64_t aRenderTimeMs)
 {
+  LOGV(("GMPVideoDecoderParent[%p]::Decode() timestamp=%lld keyframe=%d",
+        this, aInputFrame->TimeStamp(),
+        aInputFrame->FrameType() == kGMPKeyFrame));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use an dead GMP video decoder");
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
 
   GMPUniquePtr<GMPVideoEncodedFrameImpl> inputFrameImpl(
@@ -153,16 +160,18 @@ GMPVideoDecoderParent::Decode(GMPUniqueP
 
   // Async IPC, we don't have access to a return value.
   return NS_OK;
 }
 
 nsresult
 GMPVideoDecoderParent::Reset()
 {
+  LOGD(("GMPVideoDecoderParent[%p]::Reset()", this));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use an dead GMP video decoder");
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
 
   if (!SendReset()) {
@@ -173,16 +182,18 @@ GMPVideoDecoderParent::Reset()
 
   // Async IPC, we don't have access to a return value.
   return NS_OK;
 }
 
 nsresult
 GMPVideoDecoderParent::Drain()
 {
+  LOGD(("GMPVideoDecoderParent[%p]::Drain()", this));
+
   if (!mIsOpen) {
     NS_WARNING("Trying to use an dead GMP video decoder");
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
 
   if (!SendDrain()) {
@@ -206,17 +217,17 @@ GMPVideoDecoderParent::GetDisplayName() 
 
   return mPlugin->GetDisplayName();
 }
 
 // Note: Consider keeping ActorDestroy sync'd up when making changes here.
 nsresult
 GMPVideoDecoderParent::Shutdown()
 {
-  LOGD(("%s: %p", __FUNCTION__, this));
+  LOGD(("GMPVideoDecoderParent[%p]::Shutdown()", this));
   MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
 
   if (mShuttingDown) {
     return NS_OK;
   }
   mShuttingDown = true;
 
   // Ensure if we've received a shutdown while waiting for a ResetComplete
@@ -237,16 +248,18 @@ GMPVideoDecoderParent::Shutdown()
 
   return NS_OK;
 }
 
 // Note: Keep this sync'd up with Shutdown
 void
 GMPVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
 {
+  LOGD(("GMPVideoDecoderParent[%p]::ActorDestroy reason=%d", this, aWhy));
+
   mIsOpen = false;
   mActorDestroyed = true;
   mVideoHost.DoneWithAPI();
 
   // Ensure if we've received a destroy while waiting for a ResetComplete
   // or DrainComplete notification, we'll unblock the caller before processing
   // the error.
   UnblockResetAndDrain();
@@ -262,22 +275,27 @@ GMPVideoDecoderParent::ActorDestroy(Acto
     mPlugin = nullptr;
   }
   mVideoHost.ActorDestroyed();
 }
 
 bool
 GMPVideoDecoderParent::RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame)
 {
+  LOGV(("GMPVideoDecoderParent[%p]::RecvDecoded() timestamp=%lld",
+        this, aDecodedFrame.mTimestamp()));
+
   if (!mCallback) {
     return false;
   }
 
   if (!GMPVideoi420FrameImpl::CheckFrameData(aDecodedFrame)) {
-    LOG(LogLevel::Error, ("%s: Decoded frame corrupt, ignoring", __FUNCTION__));
+    LOG(LogLevel::Error,
+      ("GMPVideoDecoderParent[%p]::RecvDecoded() "
+       "timestamp=%lld decoded frame corrupt, ignoring"));
     return false;
   }
   auto f = new GMPVideoi420FrameImpl(aDecodedFrame, &mVideoHost);
 
   // Ignore any return code. It is OK for this to fail without killing the process.
   mCallback->Decoded(f);
 
   return true;
@@ -307,29 +325,33 @@ GMPVideoDecoderParent::RecvReceivedDecod
   mCallback->ReceivedDecodedFrame(aPictureId);
 
   return true;
 }
 
 bool
 GMPVideoDecoderParent::RecvInputDataExhausted()
 {
+  LOGV(("GMPVideoDecoderParent[%p]::RecvInputDataExhausted()", this));
+
   if (!mCallback) {
     return false;
   }
 
   // Ignore any return code. It is OK for this to fail without killing the process.
   mCallback->InputDataExhausted();
 
   return true;
 }
 
 bool
 GMPVideoDecoderParent::RecvDrainComplete()
 {
+  LOGD(("GMPVideoDecoderParent[%p]::RecvDrainComplete()", this));
+
   if (!mCallback) {
     return false;
   }
 
   if (!mIsAwaitingDrainComplete) {
     return true;
   }
   mIsAwaitingDrainComplete = false;
@@ -338,16 +360,18 @@ GMPVideoDecoderParent::RecvDrainComplete
   mCallback->DrainComplete();
 
   return true;
 }
 
 bool
 GMPVideoDecoderParent::RecvResetComplete()
 {
+  LOGD(("GMPVideoDecoderParent[%p]::RecvResetComplete()", this));
+
   if (!mCallback) {
     return false;
   }
 
   if (!mIsAwaitingResetComplete) {
     return true;
   }
   mIsAwaitingResetComplete = false;
@@ -356,16 +380,18 @@ GMPVideoDecoderParent::RecvResetComplete
   mCallback->ResetComplete();
 
   return true;
 }
 
 bool
 GMPVideoDecoderParent::RecvError(const GMPErr& aError)
 {
+  LOGD(("GMPVideoDecoderParent[%p]::RecvError(error=%d)", this, aError));
+
   if (!mCallback) {
     return false;
   }
 
   // Ensure if we've received an error while waiting for a ResetComplete
   // or DrainComplete notification, we'll unblock the caller before processing
   // the error.
   UnblockResetAndDrain();
@@ -374,16 +400,18 @@ GMPVideoDecoderParent::RecvError(const G
   mCallback->Error(aError);
 
   return true;
 }
 
 bool
 GMPVideoDecoderParent::RecvShutdown()
 {
+  LOGD(("GMPVideoDecoderParent[%p]::RecvShutdown()", this));
+
   Shutdown();
   return true;
 }
 
 bool
 GMPVideoDecoderParent::RecvParentShmemForPool(Shmem&& aEncodedBuffer)
 {
   if (aEncodedBuffer.IsWritable()) {
@@ -410,28 +438,32 @@ GMPVideoDecoderParent::AnswerNeedShmem(c
   *aMem = mem;
   mem = ipc::Shmem();
   return true;
 }
 
 bool
 GMPVideoDecoderParent::Recv__delete__()
 {
+  LOGD(("GMPVideoDecoderParent[%p]::Recv__delete__()", this));
+
   if (mPlugin) {
     // Ignore any return code. It is OK for this to fail without killing the process.
     mPlugin->VideoDecoderDestroyed(this);
     mPlugin = nullptr;
   }
 
   return true;
 }
 
 void
 GMPVideoDecoderParent::UnblockResetAndDrain()
 {
+  LOGD(("GMPVideoDecoderParent[%p]::UnblockResetAndDrain()", this));
+
   if (!mCallback) {
     MOZ_ASSERT(!mIsAwaitingResetComplete);
     MOZ_ASSERT(!mIsAwaitingDrainComplete);
     return;
   }
   if (mIsAwaitingResetComplete) {
     mIsAwaitingResetComplete = false;
     mCallback->ResetComplete();
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -303,24 +303,20 @@ MediaSource::EndOfStream(const Optional<
       return;
     }
     // Notify reader that all data is now available.
     mDecoder->Ended(true);
     return;
   }
   switch (aError.Value()) {
   case MediaSourceEndOfStreamError::Network:
-    // TODO: If media element has a readyState of:
-    //   HAVE_NOTHING -> run resource fetch algorithm
-    // > HAVE_NOTHING -> run "interrupted" steps of resource fetch
+    mDecoder->NetworkError();
     break;
   case MediaSourceEndOfStreamError::Decode:
-    // TODO: If media element has a readyState of:
-    //   HAVE_NOTHING -> run "unsupported" steps of resource fetch
-    // > HAVE_NOTHING -> run "corrupted" steps of resource fetch
+    mDecoder->DecodeError();
     break;
   default:
     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
   }
 }
 
 /* static */ bool
 MediaSource::IsTypeSupported(const GlobalObject&, const nsAString& aType)
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -545,16 +545,25 @@ already_AddRefed<MediaByteBuffer>
 SourceBuffer::PrepareAppend(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
 {
   typedef SourceBufferContentManager::EvictDataResult Result;
 
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
+
+  // If the HTMLMediaElement.error attribute is not null, then throw an
+  // InvalidStateError exception and abort these steps.
+  if (!mMediaSource->GetDecoder() ||
+      mMediaSource->GetDecoder()->IsEndedOrShutdown()) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   }
 
   // Eviction uses a byte threshold. If the buffer is greater than the
   // number of bytes then data is evicted. The time range for this
   // eviction is reported back to the media source. It will then
   // evict data before that range across all SourceBuffers it knows
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -973,16 +973,23 @@ TrackBuffersManager::OnDemuxerInitDone(n
     // This is handled by SourceBuffer once the promise is resolved.
     if (activeTrack) {
       mActiveTrack = true;
     }
 
     // 6. Set first initialization segment received flag to true.
     mFirstInitializationSegmentReceived = true;
   } else {
+    // Check that audio configuration hasn't changed as this is something
+    // we do not support yet (bug 1185827).
+    if (mAudioTracks.mNumTracks &&
+        (info.mAudio.mChannels != mAudioTracks.mInfo->GetAsAudioInfo()->mChannels ||
+         info.mAudio.mRate != mAudioTracks.mInfo->GetAsAudioInfo()->mRate)) {
+      RejectAppend(NS_ERROR_FAILURE, __func__);
+    }
     mAudioTracks.mLastInfo = new SharedTrackInfo(info.mAudio, streamID);
     mVideoTracks.mLastInfo = new SharedTrackInfo(info.mVideo, streamID);
   }
 
   UniquePtr<EncryptionInfo> crypto = mInputDemuxer->GetCrypto();
   if (crypto && crypto->IsEncrypted()) {
 #ifdef MOZ_EME
     // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -388,16 +388,20 @@ nsresult MediaCodecDataDecoder::InitDeco
 }
 
 // This is in usec, so that's 10ms
 #define DECODER_TIMEOUT 10000
 
 #define HANDLE_DECODER_ERROR() \
   if (NS_FAILED(res)) { \
     NS_WARNING("exiting decoder loop due to exception"); \
+    if (mDraining) { \
+      ENVOKE_CALLBACK(DrainComplete); \
+      mDraining = false; \
+    } \
     ENVOKE_CALLBACK(Error); \
     break; \
   }
 
 nsresult MediaCodecDataDecoder::GetInputBuffer(JNIEnv* env, int index, jni::Object::LocalRef* buffer)
 {
   bool retried = false;
   while (!*buffer) {
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -50,18 +50,18 @@ bool nsMixedContentBlocker::sBlockMixedS
 bool nsMixedContentBlocker::sBlockMixedDisplay = false;
 
 // Fired at the document that attempted to load mixed content.  The UI could
 // handle this event, for example, by displaying an info bar that offers the
 // choice to reload the page with mixed content permitted.
 class nsMixedContentEvent : public nsRunnable
 {
 public:
-  nsMixedContentEvent(nsISupports *aContext, MixedContentTypes aType)
-    : mContext(aContext), mType(aType)
+  nsMixedContentEvent(nsISupports *aContext, MixedContentTypes aType, bool aRootHasSecureConnection)
+    : mContext(aContext), mType(aType), mRootHasSecureConnection(aRootHasSecureConnection)
   {}
 
   NS_IMETHOD Run()
   {
     NS_ASSERTION(mContext,
                  "You can't call this runnable without a requesting context");
 
     // To update the security UI in the tab with the blocked mixed content, call
@@ -80,72 +80,106 @@ public:
     nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
     docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
     NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!");
 
     // now get the document from sameTypeRoot
     nsCOMPtr<nsIDocument> rootDoc = do_GetInterface(sameTypeRoot);
     NS_ASSERTION(rootDoc, "No root document from document shell root tree item.");
 
+    // Get eventSink and the current security state from the docShell
+    nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
+    NS_ASSERTION(eventSink, "No eventSink from docShell.");
+    nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot);
+    NS_ASSERTION(rootShell, "No root docshell from document shell root tree item.");
+    uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
+    nsCOMPtr<nsISecureBrowserUI> securityUI;
+    rootShell->GetSecurityUI(getter_AddRefs(securityUI));
+    // If there is no securityUI, document doesn't have a security state to
+    // update.  But we still want to set the document flags, so we don't return
+    // early.
+    nsresult stateRV;
+    if (securityUI) {
+      stateRV = securityUI->GetState(&state);
+    }
 
     if (mType == eMixedScript) {
        // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
        if (rootDoc->GetHasMixedActiveContentLoaded()) {
          return NS_OK;
        }
        rootDoc->SetHasMixedActiveContentLoaded(true);
 
       // Update the security UI in the tab with the allowed mixed active content
-      nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
-      if (eventSink) {
-        // If mixed display content is loaded, make sure to include that in the state.
-        if (rootDoc->GetHasMixedDisplayContentLoaded()) {
-          eventSink->OnSecurityChange(mContext,
-                                      (nsIWebProgressListener::STATE_IS_BROKEN |
-                                       nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
-                                       nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+      if (securityUI) {
+        // Bug 1182551 - before changing the security state to broken, check
+        // that the root is actually secure.
+        if (mRootHasSecureConnection) {
+          // If mixed display content is loaded, make sure to include that in the state.
+          if (rootDoc->GetHasMixedDisplayContentLoaded()) {
+            eventSink->OnSecurityChange(mContext,
+                                        (nsIWebProgressListener::STATE_IS_BROKEN |
+                                         nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
+                                         nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+          } else {
+            eventSink->OnSecurityChange(mContext,
+                                        (nsIWebProgressListener::STATE_IS_BROKEN |
+                                         nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          }
         } else {
-          eventSink->OnSecurityChange(mContext,
-                                      (nsIWebProgressListener::STATE_IS_BROKEN |
-                                       nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          // root not secure, mixed active content loaded in an https subframe
+          if (NS_SUCCEEDED(stateRV)) {
+            eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          }
         }
       }
 
     } else if (mType == eMixedDisplay) {
       // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
       if (rootDoc->GetHasMixedDisplayContentLoaded()) {
         return NS_OK;
       }
       rootDoc->SetHasMixedDisplayContentLoaded(true);
 
       // Update the security UI in the tab with the allowed mixed display content.
-      nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
-      if (eventSink) {
+      if (securityUI) {
+        // Bug 1182551 - before changing the security state to broken, check
+        // that the root is actually secure.
+        if (mRootHasSecureConnection) {
         // If mixed active content is loaded, make sure to include that in the state.
-        if (rootDoc->GetHasMixedActiveContentLoaded()) {
-          eventSink->OnSecurityChange(mContext,
-                                      (nsIWebProgressListener::STATE_IS_BROKEN |
-                                       nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
-                                       nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          if (rootDoc->GetHasMixedActiveContentLoaded()) {
+            eventSink->OnSecurityChange(mContext,
+                                        (nsIWebProgressListener::STATE_IS_BROKEN |
+                                         nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
+                                         nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          } else {
+            eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN |
+                                                   nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+          }
         } else {
-          eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN |
-          nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+          // root not secure, mixed display content loaded in an https subframe
+          if (NS_SUCCEEDED(stateRV)) {
+            eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+          }
         }
       }
     }
 
     return NS_OK;
   }
 private:
   // The requesting context for the content load. Generally, a DOM node from
   // the document that caused the load.
   nsCOMPtr<nsISupports> mContext;
 
   // The type of mixed content detected, e.g. active or display
   const MixedContentTypes mType;
+
+  // Indicates whether the top level load is https or not.
+  bool mRootHasSecureConnection;
 };
 
 
 nsMixedContentBlocker::nsMixedContentBlocker()
 {
   // Cache the pref for mixed script blocking
   Preferences::AddBoolVarCache(&sBlockMixedScript,
                                "security.mixed_content.block_active_content");
@@ -668,26 +702,26 @@ nsMixedContentBlocker::ShouldLoad(bool a
   nsCOMPtr<nsIDocument> rootDoc = do_GetInterface(sameTypeRoot);
   NS_ASSERTION(rootDoc, "No root document from document shell root tree item.");
 
   // Get eventSink and the current security state from the docShell
   nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
   NS_ASSERTION(eventSink, "No eventSink from docShell.");
   nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot);
   NS_ASSERTION(rootShell, "No root docshell from document shell root tree item.");
-  uint32_t State = nsIWebProgressListener::STATE_IS_BROKEN;
+  uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
   nsCOMPtr<nsISecureBrowserUI> securityUI;
   rootShell->GetSecurityUI(getter_AddRefs(securityUI));
   // If there is no securityUI, document doesn't have a security state.
   // Allow load and return early.
   if (!securityUI) {
     *aDecision = nsIContentPolicy::ACCEPT;
     return NS_OK;
   }
-  nsresult stateRV = securityUI->GetState(&State);
+  nsresult stateRV = securityUI->GetState(&state);
 
   // If the content is display content, and the pref says display content should be blocked, block it.
   if (sBlockMixedDisplay && classification == eMixedDisplay) {
     if (allowMixedContent) {
       LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
       *aDecision = nsIContentPolicy::ACCEPT;
       // See if mixed display content has already loaded on the page or if the state needs to be updated here.
       // If mixed display hasn't loaded previously, then we need to call OnSecurityChange() to update the UI.
@@ -707,25 +741,25 @@ nsMixedContentBlocker::ShouldLoad(bool a
           eventSink->OnSecurityChange(aRequestingContext,
                                       (nsIWebProgressListener::STATE_IS_BROKEN |
                                        nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
         }
       } else {
         // User has overriden the pref and the root is not https;
         // mixed display content was allowed on an https subframe.
         if (NS_SUCCEEDED(stateRV)) {
-          eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+          eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
          }
       }
     } else {
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
       LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
       if (!rootDoc->GetHasMixedDisplayContentBlocked() && NS_SUCCEEDED(stateRV)) {
         rootDoc->SetHasMixedDisplayContentBlocked(true);
-        eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT));
+        eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT));
       }
     }
     return NS_OK;
 
   } else if (sBlockMixedScript && classification == eMixedScript) {
     // If the content is active content, and the pref says active content should be blocked, block it
     // unless the user has choosen to override the pref
     if (allowMixedContent) {
@@ -750,48 +784,48 @@ nsMixedContentBlocker::ShouldLoad(bool a
                                       (nsIWebProgressListener::STATE_IS_BROKEN |
                                        nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
         }
         return NS_OK;
       } else {
         // User has already overriden the pref and the root is not https;
         // mixed active content was allowed on an https subframe.
         if (NS_SUCCEEDED(stateRV)) {
-          eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
         }
         return NS_OK;
       }
     } else {
       //User has not overriden the pref by Disabling protection. Reject the request and update the security state.
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
       LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
       // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
       if (rootDoc->GetHasMixedActiveContentBlocked()) {
         return NS_OK;
       }
       rootDoc->SetHasMixedActiveContentBlocked(true);
 
       // The user has not overriden the pref, so make sure they still have an option by calling eventSink
       // which will invoke the doorhanger
       if (NS_SUCCEEDED(stateRV)) {
-         eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
+         eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
       }
       return NS_OK;
     }
 
   } else {
     // The content is not blocked by the mixed content prefs.
 
     // Log a message that we are loading mixed content.
     LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
 
     // Fire the event from a script runner as it is unsafe to run script
     // from within ShouldLoad
     nsContentUtils::AddScriptRunner(
-      new nsMixedContentEvent(aRequestingContext, classification));
+      new nsMixedContentEvent(aRequestingContext, classification, rootHasSecureConnection));
     *aDecision = ACCEPT;
     return NS_OK;
   }
 
   *aDecision = REJECT_REQUEST;
   return NS_OK;
 }
 
--- a/dom/security/test/mixedcontentblocker/mochitest.ini
+++ b/dom/security/test/mixedcontentblocker/mochitest.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
+tags = mcb
 support-files =
   file_bug803225_test_mailto.html
   file_frameNavigation.html
   file_frameNavigation_blankTarget.html
   file_frameNavigation_grandchild.html
   file_frameNavigation_innermost.html
   file_frameNavigation_secure.html
   file_frameNavigation_secure_grandchild.html
--- a/dom/workers/ServiceWorker.cpp
+++ b/dom/workers/ServiceWorker.cpp
@@ -47,21 +47,16 @@ ServiceWorker::ServiceWorker(nsPIDOMWind
   : DOMEventTargetHelper(aWindow),
     mInfo(aInfo),
     mSharedWorker(aSharedWorker)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aInfo);
   MOZ_ASSERT(mSharedWorker);
 
-  if (aWindow) {
-    mDocument = aWindow->GetExtantDoc();
-    mWindow = aWindow->GetOuterWindow();
-  }
-
   // This will update our state too.
   mInfo->AppendWorker(this);
 }
 
 ServiceWorker::~ServiceWorker()
 {
   AssertIsOnMainThread();
   mInfo->RemoveWorker(this);
@@ -69,17 +64,17 @@ ServiceWorker::~ServiceWorker()
 
 NS_IMPL_ADDREF_INHERITED(ServiceWorker, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(ServiceWorker, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorker)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorker, DOMEventTargetHelper,
-                                   mSharedWorker, mDocument, mWindow)
+                                   mSharedWorker)
 
 JSObject*
 ServiceWorker::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   AssertIsOnMainThread();
 
   return ServiceWorkerBinding::Wrap(aCx, this, aGivenProto);
 }
@@ -90,30 +85,32 @@ ServiceWorker::GetScriptURL(nsString& aU
   CopyUTF8toUTF16(mInfo->ScriptSpec(), aURL);
 }
 
 void
 ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                            const Optional<Sequence<JS::Value>>& aTransferable,
                            ErrorResult& aRv)
 {
-  WorkerPrivate* workerPrivate = GetWorkerPrivate();
-  MOZ_ASSERT(workerPrivate);
-
   if (State() == ServiceWorkerState::Redundant) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  MOZ_ASSERT(mDocument && mWindow,
-             "Cannot call PostMessage on a ServiceWorker object that doesn't "
-             "have a window");
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetParentObject());
+  if (!window || !window->GetExtantDoc()) {
+    NS_WARNING("Trying to call post message from an invalid dom object.");
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
 
-  nsAutoPtr<ServiceWorkerClientInfo> clientInfo(
-    new ServiceWorkerClientInfo(mDocument, mWindow));
+  WorkerPrivate* workerPrivate = GetWorkerPrivate();
+  MOZ_ASSERT(workerPrivate);
+
+  nsAutoPtr<ServiceWorkerClientInfo> clientInfo(new ServiceWorkerClientInfo(window->GetExtantDoc()));
 
   workerPrivate->PostMessageToServiceWorker(aCx, aMessage, aTransferable,
                                             clientInfo, aRv);
 }
 
 WorkerPrivate*
 ServiceWorker::GetWorkerPrivate() const
 {
--- a/dom/workers/ServiceWorker.h
+++ b/dom/workers/ServiceWorker.h
@@ -6,17 +6,16 @@
 
 #ifndef mozilla_dom_workers_serviceworker_h__
 #define mozilla_dom_workers_serviceworker_h__
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ServiceWorkerBinding.h" // For ServiceWorkerState.
 
-class nsIDocument;
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 namespace workers {
 
 class ServiceWorkerInfo;
@@ -87,19 +86,15 @@ private:
   ServiceWorkerState mState;
   const nsRefPtr<ServiceWorkerInfo> mInfo;
 
   // To allow ServiceWorkers to potentially drop the backing DOMEventTargetHelper and
   // re-instantiate it later, they simply own a SharedWorker member that
   // can be released and recreated as required rather than re-implement some of
   // the SharedWorker logic.
   nsRefPtr<SharedWorker> mSharedWorker;
-  // We need to keep the document and window alive for PostMessage to be able
-  // to access them.
-  nsCOMPtr<nsIDocument> mDocument;
-  nsCOMPtr<nsPIDOMWindow> mWindow;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_workers_serviceworker_h__
--- a/dom/workers/ServiceWorkerClient.cpp
+++ b/dom/workers/ServiceWorkerClient.cpp
@@ -24,18 +24,17 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Se
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClient)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClient)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc,
-                                                 nsPIDOMWindow* aWindow)
+ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
   : mWindowId(0)
 {
   MOZ_ASSERT(aDoc);
   nsresult rv = aDoc->GetId(mClientId);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to get the UUID of the document.");
   }
 
@@ -50,17 +49,17 @@ ServiceWorkerClientInfo::ServiceWorkerCl
   mVisibilityState = aDoc->VisibilityState();
 
   ErrorResult result;
   mFocused = aDoc->HasFocus(result);
   if (result.Failed()) {
     NS_WARNING("Failed to get focus information.");
   }
 
-  nsRefPtr<nsGlobalWindow> outerWindow = static_cast<nsGlobalWindow*>(aWindow);
+  nsRefPtr<nsGlobalWindow> outerWindow = static_cast<nsGlobalWindow*>(aDoc->GetWindow());
   MOZ_ASSERT(outerWindow);
   if (!outerWindow->IsTopLevelWindow()) {
     mFrameType = FrameType::Nested;
   } else if (outerWindow->HadOriginalOpener()) {
     mFrameType = FrameType::Auxiliary;
   } else {
     mFrameType = FrameType::Top_level;
   }
--- a/dom/workers/ServiceWorkerClient.h
+++ b/dom/workers/ServiceWorkerClient.h
@@ -10,34 +10,33 @@
 
 #include "nsCOMPtr.h"
 #include "nsWrapperCache.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ClientBinding.h"
 
 class nsIDocument;
-class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 namespace workers {
 
 class ServiceWorkerClient;
 class ServiceWorkerWindowClient;
 
 // Used as a container object for information needed to create
 // client objects.
 class ServiceWorkerClientInfo final
 {
   friend class ServiceWorkerClient;
   friend class ServiceWorkerWindowClient;
 
 public:
-  ServiceWorkerClientInfo(nsIDocument* aDoc, nsPIDOMWindow* aWindow);
+  explicit ServiceWorkerClientInfo(nsIDocument* aDoc);
 
 private:
   nsString mClientId;
   uint64_t mWindowId;
   nsString mUrl;
 
   // Window Clients
   VisibilityState mVisibilityState;
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -3891,17 +3891,17 @@ ServiceWorkerManager::DispatchFetchEvent
     NS_NewRunnableMethod(aChannel, &nsIInterceptedChannel::ResetInterception);
 
   nsAutoPtr<ServiceWorkerClientInfo> clientInfo;
 
   if (!isNavigation) {
     MOZ_ASSERT(aDoc);
     aRv = GetDocumentController(aDoc->GetInnerWindow(), failRunnable,
                                 getter_AddRefs(serviceWorker));
-    clientInfo = new ServiceWorkerClientInfo(aDoc, aDoc->GetWindow());
+    clientInfo = new ServiceWorkerClientInfo(aDoc);
   } else {
     nsCOMPtr<nsIChannel> internalChannel;
     aRv = aChannel->GetChannel(getter_AddRefs(internalChannel));
     if (NS_WARN_IF(aRv.Failed())) {
       return;
     }
 
     nsCOMPtr<nsIURI> uri;
@@ -4271,17 +4271,17 @@ EnumControlledDocuments(nsISupports* aKe
   }
 
   nsCOMPtr<nsIDocument> document = do_QueryInterface(aKey);
 
   if (!document || !document->GetWindow()) {
     return PL_DHASH_NEXT;
   }
 
-  ServiceWorkerClientInfo clientInfo(document, document->GetWindow());
+  ServiceWorkerClientInfo clientInfo(document);
   data->mDocuments.AppendElement(clientInfo);
 
   return PL_DHASH_NEXT;
 }
 
 static void
 FireControllerChangeOnDocument(nsIDocument* aDocument)
 {
--- a/dom/workers/ServiceWorkerWindowClient.cpp
+++ b/dom/workers/ServiceWorkerWindowClient.cpp
@@ -85,19 +85,24 @@ public:
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
     nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
     UniquePtr<ServiceWorkerClientInfo> clientInfo;
 
     if (window) {
-      nsContentUtils::DispatchChromeEvent(window->GetExtantDoc(), window->GetOuterWindow(), NS_LITERAL_STRING("DOMServiceWorkerFocusClient"), true, true);
-      clientInfo.reset(new ServiceWorkerClientInfo(window->GetDocument(),
-                                                   window->GetOuterWindow()));
+      nsCOMPtr<nsIDocument> doc = window->GetDocument();
+      if (doc) {
+        nsContentUtils::DispatchChromeEvent(doc,
+                                            window->GetOuterWindow(),
+                                            NS_LITERAL_STRING("DOMServiceWorkerFocusClient"),
+                                            true, true);
+        clientInfo.reset(new ServiceWorkerClientInfo(doc));
+      }
     }
 
     DispatchResult(Move(clientInfo));
     return NS_OK;
   }
 
 private:
   void
--- a/dom/xbl/nsXBLPrototypeBinding.cpp
+++ b/dom/xbl/nsXBLPrototypeBinding.cpp
@@ -896,17 +896,18 @@ nsXBLPrototypeBinding::Read(nsIObjectInp
   }
 
   uint32_t interfaceCount;
   rv = aStream->Read32(&interfaceCount);
   NS_ENSURE_SUCCESS(rv, rv);
 
   for (; interfaceCount > 0; interfaceCount--) {
     nsIID iid;
-    aStream->ReadID(&iid);
+    rv = aStream->ReadID(&iid);
+    NS_ENSURE_SUCCESS(rv, rv);
     mInterfaceTable.Put(iid, mBinding);
   }
 
   AutoSafeJSContext cx;
   JS::Rooted<JSObject*> compilationGlobal(cx, xpc::CompilationScope());
   JSAutoCompartment ac(cx, compilationGlobal);
 
   bool isFirstBinding = aFlags & XBLBinding_Serialize_IsFirstBinding;
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -235,19 +235,16 @@ XULDocument::~XULDocument()
 nsresult
 NS_NewXULDocument(nsIXULDocument** result)
 {
     NS_PRECONDITION(result != nullptr, "null ptr");
     if (! result)
         return NS_ERROR_NULL_POINTER;
 
     XULDocument* doc = new XULDocument();
-    if (! doc)
-        return NS_ERROR_OUT_OF_MEMORY;
-
     NS_ADDREF(doc);
 
     nsresult rv;
     if (NS_FAILED(rv = doc->Init())) {
         NS_RELEASE(doc);
         return rv;
     }
 
@@ -444,18 +441,16 @@ XULDocument::StartDocumentLoad(const cha
         // Set up the right principal on ourselves.
         SetPrincipal(proto->DocumentPrincipal());
 
         // We need a listener, even if proto is not yet loaded, in which
         // event the listener's OnStopRequest method does nothing, and all
         // the interesting work happens below XULDocument::EndLoad, from
         // the call there to mCurrentPrototype->NotifyLoadDone().
         *aDocListener = new CachedChromeStreamListener(this, loaded);
-        if (! *aDocListener)
-            return NS_ERROR_OUT_OF_MEMORY;
     }
     else {
         bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
         bool fillXULCache = (useXULCache && IsChromeURI(mDocumentURI));
 
 
         // It's just a vanilla document load. Create a parser to deal
         // with the stream n' stuff.
@@ -1630,19 +1625,16 @@ XULDocument::AddElementToDocumentPre(Ele
     bool listener, resolved;
     rv = CheckBroadcasterHookup(aElement, &listener, &resolved);
     if (NS_FAILED(rv)) return rv;
 
     // If it's not there yet, we may be able to defer hookup until
     // later.
     if (listener && !resolved && (mResolutionPhase != nsForwardReference::eDone)) {
         BroadcasterHookup* hookup = new BroadcasterHookup(this, aElement);
-        if (! hookup)
-            return NS_ERROR_OUT_OF_MEMORY;
-
         rv = AddForwardReference(hookup);
         if (NS_FAILED(rv)) return rv;
     }
 
     return NS_OK;
 }
 
 nsresult
@@ -1663,19 +1655,16 @@ XULDocument::AddElementToDocumentPost(El
     if (needsHookup) {
         if (mResolutionPhase == nsForwardReference::eDone) {
             rv = CreateTemplateBuilder(aElement);
             if (NS_FAILED(rv))
                 return rv;
         }
         else {
             TemplateBuilderHookup* hookup = new TemplateBuilderHookup(aElement);
-            if (! hookup)
-                return NS_ERROR_OUT_OF_MEMORY;
-
             rv = AddForwardReference(hookup);
             if (NS_FAILED(rv))
                 return rv;
         }
     }
 
     return NS_OK;
 }
@@ -1870,17 +1859,16 @@ XULDocument::Clone(mozilla::dom::NodeInf
 nsresult
 XULDocument::Init()
 {
     nsresult rv = XMLDocument::Init();
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Create our command dispatcher and hook it up.
     mCommandDispatcher = new nsXULCommandDispatcher(this);
-    NS_ENSURE_TRUE(mCommandDispatcher, NS_ERROR_OUT_OF_MEMORY);
 
     if (gRefCnt++ == 0) {
         // ensure that the XUL prototype cache is instantiated successfully,
         // so that we can use nsXULPrototypeCache::GetInstance() without
         // null-checks in the rest of the class.
         nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
         if (!cache) {
           NS_ERROR("Could not instantiate nsXULPrototypeCache");
@@ -2002,17 +1990,16 @@ XULDocument::PrepareToLoadPrototype(nsIU
         mMasterPrototype = mCurrentPrototype;
         // Set our principal based on the master proto.
         SetPrincipal(aDocumentPrincipal);
     }
 
     // Create a XUL content sink, a parser, and kick off a load for
     // the overlay.
     nsRefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl();
-    if (!sink) return NS_ERROR_OUT_OF_MEMORY;
 
     rv = sink->Init(this, mCurrentPrototype);
     NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink");
     if (NS_FAILED(rv)) return rv;
 
     nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
     NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create parser");
     if (NS_FAILED(rv)) return rv;
@@ -2190,19 +2177,16 @@ XULDocument::ContextStack::~ContextStack
     }
 }
 
 nsresult
 XULDocument::ContextStack::Push(nsXULPrototypeElement* aPrototype,
                                 nsIContent* aElement)
 {
     Entry* entry = new Entry;
-    if (! entry)
-        return NS_ERROR_OUT_OF_MEMORY;
-
     entry->mPrototype = aPrototype;
     entry->mElement   = aElement;
     NS_IF_ADDREF(entry->mElement);
     entry->mIndex     = 0;
 
     entry->mNext = mTop;
     mTop = entry;
 
@@ -2647,19 +2631,16 @@ XULDocument::LoadOverlayInternal(nsIURI*
         if (! listener)
             return NS_ERROR_UNEXPECTED;
 
         // Add an observer to the parser; this'll get called when
         // Necko fires its On[Start|Stop]Request() notifications,
         // and will let us recover from a missing overlay.
         ParserObserver* parserObserver =
             new ParserObserver(this, mCurrentPrototype);
-        if (! parserObserver)
-            return NS_ERROR_OUT_OF_MEMORY;
-
         NS_ADDREF(parserObserver);
         parser->Parse(aURI, parserObserver);
         NS_RELEASE(parserObserver);
 
         nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
         nsCOMPtr<nsIChannel> channel;
         // Set the owner of the channel to be our principal so
         // that the overlay's JSObjects etc end up being created
@@ -3627,18 +3608,16 @@ XULDocument::CreateOverlayElement(nsXULP
     nsresult rv;
 
     nsRefPtr<Element> element;
     rv = CreateElementFromPrototype(aPrototype, getter_AddRefs(element), false);
     if (NS_FAILED(rv)) return rv;
 
     OverlayForwardReference* fwdref =
         new OverlayForwardReference(this, element);
-    if (! fwdref)
-        return NS_ERROR_OUT_OF_MEMORY;
 
     // transferring ownership to ya...
     rv = AddForwardReference(fwdref);
     if (NS_FAILED(rv)) return rv;
 
     element.forget(aResult);
     return NS_OK;
 }
--- a/dom/xul/nsXULCommandDispatcher.cpp
+++ b/dom/xul/nsXULCommandDispatcher.cpp
@@ -305,21 +305,17 @@ nsXULCommandDispatcher::AddCommandUpdate
            ("xulcmd[%p] add     %p(events=%s targets=%s)",
             this, aElement,
             aeventsC.get(),
             atargetsC.get()));
   }
 #endif
 
   // If we get here, this is a new updater. Append it to the list.
-  updater = new Updater(aElement, aEvents, aTargets);
-  if (! updater)
-      return NS_ERROR_OUT_OF_MEMORY;
-
-  *link = updater;
+  *link = new Updater(aElement, aEvents, aTargets);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULCommandDispatcher::RemoveCommandUpdater(nsIDOMElement* aElement)
 {
   NS_PRECONDITION(aElement != nullptr, "null ptr");
   if (! aElement)
--- a/dom/xul/nsXULControllers.cpp
+++ b/dom/xul/nsXULControllers.cpp
@@ -47,19 +47,16 @@ nsXULControllers::DeleteControllers()
 nsresult
 NS_NewXULControllers(nsISupports* aOuter, REFNSIID aIID, void** aResult)
 {
   NS_PRECONDITION(aOuter == nullptr, "no aggregation");
   if (aOuter)
     return NS_ERROR_NO_AGGREGATION;
 
   nsXULControllers* controllers = new nsXULControllers();
-  if (! controllers)
-    return NS_ERROR_OUT_OF_MEMORY;
-  
   nsresult rv;
   NS_ADDREF(controllers);
   rv = controllers->QueryInterface(aIID, aResult);
   NS_RELEASE(controllers);
   return rv;
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULControllers)
@@ -116,17 +113,16 @@ nsXULControllers::GetControllerForComman
   
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULControllers::InsertControllerAt(uint32_t aIndex, nsIController *controller)
 {
   nsXULControllerData*  controllerData = new nsXULControllerData(++mCurControllerID, controller);
-  if (!controllerData) return NS_ERROR_OUT_OF_MEMORY;
 #ifdef DEBUG
   nsXULControllerData** inserted =
 #endif
   mControllers.InsertElementAt(aIndex, controllerData);
   NS_ASSERTION(inserted != nullptr, "Insertion of controller failed");
   return NS_OK;
 }
 
@@ -160,17 +156,16 @@ nsXULControllers::GetControllerAt(uint32
   return controllerData->GetController(_retval);   // does the addref  
 }
 
 NS_IMETHODIMP
 nsXULControllers::AppendController(nsIController *controller)
 {
   // This assigns controller IDs starting at 1 so we can use 0 to test if an ID was obtained
   nsXULControllerData*  controllerData = new nsXULControllerData(++mCurControllerID, controller);
-  if (!controllerData) return NS_ERROR_OUT_OF_MEMORY;
 
 #ifdef DEBUG
   nsXULControllerData** appended =
 #endif
   mControllers.AppendElement(controllerData);
   NS_ASSERTION(appended != nullptr, "Appending controller failed");
   return NS_OK;
 }
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -2273,156 +2273,127 @@ nsXULPrototypeElement::Deserialize(nsIOb
                                    nsIURI* aDocumentURI,
                                    const nsTArray<nsRefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
 {
     NS_PRECONDITION(aNodeInfos, "missing nodeinfo array");
 
     // Read Node Info
     uint32_t number = 0;
     nsresult rv = aStream->Read32(&number);
+    if (NS_WARN_IF(NS_FAILED(rv))) return rv;
     mNodeInfo = aNodeInfos->SafeElementAt(number, nullptr);
     if (!mNodeInfo) {
         return NS_ERROR_UNEXPECTED;
     }
 
     // Read Attributes
-    nsresult tmp = aStream->Read32(&number);
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
+    rv = aStream->Read32(&number);
+    if (NS_WARN_IF(NS_FAILED(rv))) return rv;
     mNumAttributes = int32_t(number);
 
     if (mNumAttributes > 0) {
         mAttributes = new (fallible) nsXULPrototypeAttribute[mNumAttributes];
         if (!mAttributes) {
             return NS_ERROR_OUT_OF_MEMORY;
         }
 
         nsAutoString attributeValue;
         for (uint32_t i = 0; i < mNumAttributes; ++i) {
-            tmp = aStream->Read32(&number);
-            if (NS_FAILED(tmp)) {
-              rv = tmp;
-            }
+            rv = aStream->Read32(&number);
+            if (NS_WARN_IF(NS_FAILED(rv))) return rv;
             mozilla::dom::NodeInfo* ni = aNodeInfos->SafeElementAt(number, nullptr);
             if (!ni) {
                 return NS_ERROR_UNEXPECTED;
             }
 
             mAttributes[i].mName.SetTo(ni);
 
-            tmp = aStream->ReadString(attributeValue);
-            if (NS_FAILED(tmp)) {
-              rv = tmp;
-            }
-            tmp = SetAttrAt(i, attributeValue, aDocumentURI);
-            if (NS_FAILED(tmp)) {
-              rv = tmp;
-            }
+            rv = aStream->ReadString(attributeValue);
+            if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+            rv = SetAttrAt(i, attributeValue, aDocumentURI);
+            if (NS_WARN_IF(NS_FAILED(rv))) return rv;
         }
     }
 
-    tmp = aStream->Read32(&number);
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
+    rv = aStream->Read32(&number);
+    if (NS_WARN_IF(NS_FAILED(rv))) return rv;
     uint32_t numChildren = int32_t(number);
 
     if (numChildren > 0) {
-        mChildren.SetCapacity(numChildren);
+        if (!mChildren.SetCapacity(numChildren, fallible)) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
 
         for (uint32_t i = 0; i < numChildren; i++) {
-            tmp = aStream->Read32(&number);
-            if (NS_FAILED(tmp)) {
-              rv = tmp;
-            }
+            rv = aStream->Read32(&number);
+            if (NS_WARN_IF(NS_FAILED(rv))) return rv;
             Type childType = (Type)number;
 
             nsRefPtr<nsXULPrototypeNode> child;
 
             switch (childType) {
             case eType_Element:
                 child = new nsXULPrototypeElement();
-                child->mType = childType;
-
-                tmp = child->Deserialize(aStream, aProtoDoc, aDocumentURI,
-                                         aNodeInfos);
-                if (NS_FAILED(tmp)) {
-                  rv = tmp;
-                }
+                rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI,
+                                        aNodeInfos);
+                if (NS_WARN_IF(NS_FAILED(rv))) return rv;
                 break;
             case eType_Text:
                 child = new nsXULPrototypeText();
-                child->mType = childType;
-
-                tmp = child->Deserialize(aStream, aProtoDoc, aDocumentURI,
-                                         aNodeInfos);
-                if (NS_FAILED(tmp)) {
-                  rv = tmp;
-                }
+                rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI,
+                                        aNodeInfos);
+                if (NS_WARN_IF(NS_FAILED(rv))) return rv;
                 break;
             case eType_PI:
                 child = new nsXULPrototypePI();
-                child->mType = childType;
-
-                tmp = child->Deserialize(aStream, aProtoDoc, aDocumentURI,
-                                         aNodeInfos);
-                if (NS_FAILED(tmp)) {
-                  rv = tmp;
-                }
+                rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI,
+                                        aNodeInfos);
+                if (NS_WARN_IF(NS_FAILED(rv))) return rv;
                 break;
             case eType_Script: {
                 // language version/options obtained during deserialization.
-                nsXULPrototypeScript* script = new nsXULPrototypeScript(0, 0);
-                child = script;
-                child->mType = childType;
-
-                tmp = aStream->ReadBoolean(&script->mOutOfLine);
-                if (NS_FAILED(tmp)) {
-                  rv = tmp;
-                }
-                if (! script->mOutOfLine) {
-                    tmp = script->Deserialize(aStream, aProtoDoc, aDocumentURI,
-                                              aNodeInfos);
-                    if (NS_FAILED(tmp)) {
-                      rv = tmp;
-                    }
+                nsRefPtr<nsXULPrototypeScript> script = new nsXULPrototypeScript(0, 0);
+
+                rv = aStream->ReadBoolean(&script->mOutOfLine);
+                if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+                if (!script->mOutOfLine) {
+                    rv = script->Deserialize(aStream, aProtoDoc, aDocumentURI,
+                                             aNodeInfos);
+                    if (NS_WARN_IF(NS_FAILED(rv))) return rv;
                 } else {
                     nsCOMPtr<nsISupports> supports;
-                    tmp = aStream->ReadObject(true, getter_AddRefs(supports));
+                    rv = aStream->ReadObject(true, getter_AddRefs(supports));
+                    if (NS_WARN_IF(NS_FAILED(rv))) return rv;
                     script->mSrcURI = do_QueryInterface(supports);
-                    if (NS_FAILED(tmp)) {
-                      rv = tmp;
-                    }
-
-                    tmp = script->DeserializeOutOfLine(aStream, aProtoDoc);
-                    if (NS_FAILED(tmp)) {
-                      rv = tmp;
-                    }
+
+                    rv = script->DeserializeOutOfLine(aStream, aProtoDoc);
+                    if (NS_WARN_IF(NS_FAILED(rv))) return rv;
                 }
-                // If we failed to deserialize, consider deleting 'script'?
+
+                child = script.forget();
                 break;
             }
             default:
                 MOZ_ASSERT(false, "Unexpected child type!");
                 return NS_ERROR_UNEXPECTED;
             }
 
             MOZ_ASSERT(child, "Don't append null to mChildren");
+            MOZ_ASSERT(child->mType == childType);
             mChildren.AppendElement(child);
 
             // Oh dear. Something failed during the deserialization.
             // We don't know what.  But likely consequences of failed
             // deserializations included calls to |AbortCaching| which
             // shuts down the cache and closes our streams.
             // If that happens, next time through this loop, we die a messy
             // death. So, let's just fail now, and propagate that failure
             // upward so that the ChromeProtocolHandler knows it can't use
             // a cached chrome channel for this.
-            if (NS_FAILED(rv))
+            if (NS_WARN_IF(NS_FAILED(rv)))
                 return rv;
         }
     }
 
     return rv;
 }
 
 nsresult
@@ -2612,32 +2583,35 @@ nsXULPrototypeScript::SerializeOutOfLine
 
 
 nsresult
 nsXULPrototypeScript::Deserialize(nsIObjectInputStream* aStream,
                                   nsXULPrototypeDocument* aProtoDoc,
                                   nsIURI* aDocumentURI,
                                   const nsTArray<nsRefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
 {
+    nsresult rv;
     NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr ||
                  !mScriptObject,
                  "prototype script not well-initialized when deserializing?!");
 
     // Read basic prototype data
-    aStream->Read32(&mLineNo);
-    aStream->Read32(&mLangVersion);
+    rv = aStream->Read32(&mLineNo);
+    if (NS_FAILED(rv)) return rv;
+    rv = aStream->Read32(&mLangVersion);
+    if (NS_FAILED(rv)) return rv;
 
     AutoSafeJSContext cx;
     JS::Rooted<JSObject*> global(cx, xpc::CompilationScope());
     NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
     JSAutoCompartment ac(cx, global);
 
     JS::Rooted<JSScript*> newScriptObject(cx);
-    nsresult rv = nsContentUtils::XPConnect()->ReadScript(aStream, cx,
-                                                          newScriptObject.address());
+    rv = nsContentUtils::XPConnect()->ReadScript(aStream, cx,
+                                                 newScriptObject.address());
     NS_ENSURE_SUCCESS(rv, rv);
     Set(newScriptObject);
     return NS_OK;
 }
 
 
 nsresult
 nsXULPrototypeScript::DeserializeOutOfLine(nsIObjectInputStream* aInput,
@@ -2857,21 +2831,21 @@ nsXULPrototypeText::Serialize(nsIObjectO
 }
 
 nsresult
 nsXULPrototypeText::Deserialize(nsIObjectInputStream* aStream,
                                 nsXULPrototypeDocument* aProtoDoc,
                                 nsIURI* aDocumentURI,
                                 const nsTArray<nsRefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
 {
-    nsresult rv;
-
-    rv = aStream->ReadString(mValue);
-
-    return rv;
+    nsresult rv = aStream->ReadString(mValue);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+    return NS_OK;
 }
 
 //----------------------------------------------------------------------
 //
 // nsXULPrototypePI
 //
 
 nsresult
@@ -2900,15 +2874,14 @@ nsresult
 nsXULPrototypePI::Deserialize(nsIObjectInputStream* aStream,
                               nsXULPrototypeDocument* aProtoDoc,
                               nsIURI* aDocumentURI,
                               const nsTArray<nsRefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
 {
     nsresult rv;
 
     rv = aStream->ReadString(mTarget);
-    nsresult tmp = aStream->ReadString(mData);
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
+    if (NS_FAILED(rv)) return rv;
+    rv = aStream->ReadString(mData);
+    if (NS_FAILED(rv)) return rv;
 
     return rv;
 }
--- a/dom/xul/nsXULPrototypeDocument.cpp
+++ b/dom/xul/nsXULPrototypeDocument.cpp
@@ -49,18 +49,16 @@ nsXULPrototypeDocument::nsXULPrototypeDo
     ++gRefCnt;
 }
 
 
 nsresult
 nsXULPrototypeDocument::Init()
 {
     mNodeInfoManager = new nsNodeInfoManager();
-    NS_ENSURE_TRUE(mNodeInfoManager, NS_ERROR_OUT_OF_MEMORY);
-
     return mNodeInfoManager->Init(nullptr);
 }
 
 nsXULPrototypeDocument::~nsXULPrototypeDocument()
 {
     if (mRoot)
         mRoot->ReleaseSubtree();
 }
@@ -144,18 +142,16 @@ nsXULPrototypeDocument::Read(nsIObjectIn
     principal = do_QueryInterface(supports);
     if (NS_FAILED(tmp)) {
       rv = tmp;
     }
     // Better safe than sorry....
     mNodeInfoManager->SetDocumentPrincipal(principal);
 
     mRoot = new nsXULPrototypeElement();
-    if (! mRoot)
-       return NS_ERROR_OUT_OF_MEMORY;
 
     // mozilla::dom::NodeInfo table
     nsTArray<nsRefPtr<mozilla::dom::NodeInfo>> nodeInfos;
 
     tmp = aStream->Read32(&count);
     if (NS_FAILED(tmp)) {
       rv = tmp;
     }
@@ -203,20 +199,16 @@ nsXULPrototypeDocument::Read(nsIObjectIn
     while (NS_SUCCEEDED(rv)) {
         tmp = aStream->Read32(&type);
         if (NS_FAILED(tmp)) {
           rv = tmp;
         }
 
         if ((nsXULPrototypeNode::Type)type == nsXULPrototypeNode::eType_PI) {
             nsRefPtr<nsXULPrototypePI> pi = new nsXULPrototypePI();
-            if (! pi) {
-               rv = NS_ERROR_OUT_OF_MEMORY;
-               break;
-            }
 
             tmp = pi->Deserialize(aStream, this, mURI, &nodeInfos);
             if (NS_FAILED(tmp)) {
               rv = tmp;
             }
             tmp = AddProcessingInstruction(pi);
             if (NS_FAILED(tmp)) {
               rv = tmp;
--- a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
@@ -158,71 +158,89 @@ WebBrowserPersistLocalDocument::GetRefer
     mDocument->GetReferrer(aReferrer);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD)
 {
     nsCOMPtr<nsIDOMWindow> window = mDocument->GetDefaultView();
-    NS_ENSURE_STATE(window);
+    if (NS_WARN_IF(!window)) {
+        aCD.SetIsVoid(true);
+        return NS_OK;
+    }
     nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
-    NS_ENSURE_STATE(utils);
-    return utils->GetDocumentMetadata(
+    if (NS_WARN_IF(!utils)) {
+        aCD.SetIsVoid(true);
+        return NS_OK;
+    }
+    nsresult rv = utils->GetDocumentMetadata(
         NS_LITERAL_STRING("content-disposition"), aCD);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        aCD.SetIsVoid(true);
+    }
+    return NS_OK;
 }
 
 NS_IMETHODIMP
 WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey)
 {
-    nsCOMPtr<nsISHEntry> history;
-    nsresult rv = GetHistory(getter_AddRefs(history));
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_STATE(history);
+    nsCOMPtr<nsISHEntry> history = GetHistory();
+    if (!history) {
+        *aKey = 0;
+        return NS_OK;
+    }
     nsCOMPtr<nsISupports> abstractKey;
-    rv = history->GetCacheKey(getter_AddRefs(abstractKey));
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (!abstractKey) {
+    nsresult rv = history->GetCacheKey(getter_AddRefs(abstractKey));
+    if (NS_WARN_IF(NS_FAILED(rv)) || !abstractKey) {
         *aKey = 0;
         return NS_OK;
     }
     nsCOMPtr<nsISupportsPRUint32> u32 = do_QueryInterface(abstractKey);
     if (NS_WARN_IF(!u32)) {
         *aKey = 0;
         return NS_OK;
     }
     return u32->GetData(aKey);
 }
 
 NS_IMETHODIMP
 WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream)
 {
-    nsCOMPtr<nsISHEntry> history;
-    nsresult rv = GetHistory(getter_AddRefs(history));
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_STATE(history);
+    nsCOMPtr<nsISHEntry> history = GetHistory();
+    if (!history) {
+        *aStream = nullptr;
+        return NS_OK;
+    }
     return history->GetPostData(aStream);
 }
 
-nsresult
-WebBrowserPersistLocalDocument::GetHistory(nsISHEntry** aHistory)
+already_AddRefed<nsISHEntry>
+WebBrowserPersistLocalDocument::GetHistory()
 {
     nsCOMPtr<nsIDOMWindow> window = mDocument->GetDefaultView();
-    NS_ENSURE_STATE(window);
+    if (NS_WARN_IF(!window)) {
+        return nullptr;
+    }
     nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
-    NS_ENSURE_STATE(webNav);
+    if (NS_WARN_IF(!webNav)) {
+        return nullptr;
+    }
     nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav);
-    NS_ENSURE_STATE(desc);
+    if (NS_WARN_IF(!desc)) {
+        return nullptr;
+    }
     nsCOMPtr<nsISupports> curDesc;
     nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc));
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_STATE(curDesc);
+    // This can fail if, e.g., the document is a Print Preview.
+    if (NS_FAILED(rv) || NS_WARN_IF(!curDesc)) {
+        return nullptr;
+    }
     nsCOMPtr<nsISHEntry> history = do_QueryInterface(curDesc);
-    history.forget(aHistory);
-    return NS_OK;
+    return history.forget();
 }
 
 const nsCString&
 WebBrowserPersistLocalDocument::GetCharacterSet() const
 {
     return mDocument->GetDocumentCharacterSet();
 }
 
@@ -323,17 +341,20 @@ nsresult
 ResourceReader::OnWalkSubframe(nsIDOMNode* aNode)
 {
     nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(aNode);
     NS_ENSURE_STATE(loaderOwner);
     nsRefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
     NS_ENSURE_STATE(loader);
 
     ++mOutstandingDocuments;
-    nsresult rv = loader->StartPersistence(this);
+    // Pass in 0 as the outer window ID so that we start
+    // persisting the root of this subframe, and not some other
+    // subframe child of this subframe.
+    nsresult rv = loader->StartPersistence(0, this);
     if (NS_FAILED(rv)) {
         if (rv == NS_ERROR_NO_CONTENT) {
             // Just ignore frames with no content document.
             rv = NS_OK;
         }
         // StartPersistence won't eventually call this if it failed,
         // so this does so (to keep mOutstandingDocuments correct).
         DocumentDone(rv);
--- a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h
@@ -36,16 +36,16 @@ public:
 private:
     nsCOMPtr<nsIDocument> mDocument;
     uint32_t mPersistFlags;
 
     void DecideContentType(nsACString& aContentType);
     nsresult GetDocEncoder(const nsACString& aContentType,
                            uint32_t aEncoderFlags,
                            nsIDocumentEncoder** aEncoder);
-    nsresult GetHistory(nsISHEntry** aHistory);
+    already_AddRefed<nsISHEntry> GetHistory();
 
     virtual ~WebBrowserPersistLocalDocument();
 };
 
 } // namespace mozilla
 
 #endif // WebBrowserPersistLocalDocument_h__
--- a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
@@ -32,17 +32,21 @@ WebBrowserPersistResourcesChild::VisitRe
 }
 
 NS_IMETHODIMP
 WebBrowserPersistResourcesChild::VisitDocument(nsIWebBrowserPersistDocument* aDocument,
                                                nsIWebBrowserPersistDocument* aSubDocument)
 {
     auto* subActor = new WebBrowserPersistDocumentChild();
     dom::PBrowserChild* grandManager = Manager()->Manager();
-    if (!grandManager->SendPWebBrowserPersistDocumentConstructor(subActor)) {
+    // As a consequence of how PWebBrowserPersistDocumentConstructor can be
+    // sent by both the parent and the child, we must pass the outerWindowID
+    // argument here. We pass 0, though note that this argument is actually
+    // just ignored when passed up to the parent from the child.
+    if (!grandManager->SendPWebBrowserPersistDocumentConstructor(subActor, 0)) {
         // NOTE: subActor is freed at this point.
         return NS_ERROR_FAILURE;
     }
     // ...but here, IPC won't free subActor until after this returns
     // to the event loop.
 
     // The order of these two messages will be preserved, because
     // they're the same toplevel protocol and priority.
--- a/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl
+++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl
@@ -17,14 +17,25 @@ interface nsIWebBrowserPersistDocumentRe
  *
  * Warning: this is currently implemented only by nsFrameLoader, and
  * may change in the future to become more frame-loader-specific or be
  * merged into nsIFrameLoader.  See bug 1101100 comment #34.
  *
  * @see nsIWebBrowserPersistDocumentReceiver
  * @see nsIWebBrowserPersistDocument
  * @see nsIWebBrowserPersist
+ *
+ * @param aOuterWindowID
+ *        The outer window ID of the subframe we'd like to persist.
+ *        If set at 0, nsIWebBrowserPersistable will attempt to persist
+ *        the top-level document. If the outer window ID is for a subframe
+ *        that does not exist, or is not held beneath the nsIWebBrowserPersistable,
+ *        aRecv's onError method will be called with NS_ERROR_NO_CONTENT.
+ * @param aRecv
+ *        The nsIWebBrowserPersistDocumentReceiver is a callback that
+ *        will be fired once the document is ready for persisting.
  */
-[scriptable, function, uuid(24d0dc9e-b970-4cca-898f-cbba03abaa73)]
+[scriptable, uuid(f4c3fa8e-83e9-49f8-ac6f-951fc7541fe4)]
 interface nsIWebBrowserPersistable : nsISupports
 {
-  void startPersistence(in nsIWebBrowserPersistDocumentReceiver aRecv);
+  void startPersistence(in unsigned long long aOuterWindowID,
+                        in nsIWebBrowserPersistDocumentReceiver aRecv);
 };
--- a/gfx/2d/moz.build
+++ b/gfx/2d/moz.build
@@ -32,16 +32,17 @@ EXPORTS.mozilla.gfx += [
     'PatternHelpers.h',
     'Point.h',
     'Quaternion.h',
     'Rect.h',
     'Scale.h',
     'ScaleFactor.h',
     'ScaleFactors2D.h',
     'SourceSurfaceCairo.h',
+    'StackArray.h',
     'Tools.h',
     'Types.h',
     'UserData.h',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     EXPORTS.mozilla.gfx += [
         'MacIOSurface.h',
--- a/gfx/layers/composite/ImageHost.cpp
+++ b/gfx/layers/composite/ImageHost.cpp
@@ -276,16 +276,19 @@ ImageHost::Composite(LayerComposite* aLa
 
   if (uint32_t(imageIndex) + 1 < mImages.Length()) {
     GetCompositor()->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS));
   }
 
   TimedImage* img = &mImages[imageIndex];
   // Make sure the front buffer has a compositor
   img->mFrontBuffer->SetCompositor(GetCompositor());
+  if (img->mTextureSource) {
+    img->mTextureSource->SetCompositor(GetCompositor());
+  }
 
   {
     AutoLockCompositableHost autoLock(this);
     if (autoLock.Failed()) {
       NS_WARNING("failed to lock front buffer");
       return;
     }
 
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -13,16 +13,17 @@
 #include "nsIWidget.h"
 #include "mozilla/layers/ImageHost.h"
 #include "mozilla/layers/ContentHost.h"
 #include "mozilla/layers/Effects.h"
 #include "nsWindowsHelpers.h"
 #include "gfxPrefs.h"
 #include "gfxCrashReporterUtils.h"
 #include "gfxVR.h"
+#include "mozilla/gfx/StackArray.h"
 
 #include "mozilla/EnumeratedArray.h"
 
 #include <dxgi1_2.h>
 
 namespace mozilla {
 
 using namespace gfx;
@@ -1086,33 +1087,30 @@ CompositorD3D11::EndFrame()
 
   if (oldSize == mSize) {
     RefPtr<IDXGISwapChain1> chain;
     HRESULT hr = mSwapChain->QueryInterface((IDXGISwapChain1**)byRef(chain));
     if (SUCCEEDED(hr) && chain) {
       DXGI_PRESENT_PARAMETERS params;
       PodZero(&params);
       params.DirtyRectsCount = mInvalidRegion.GetNumRects();
-      std::vector<RECT> rects;
-      rects.reserve(params.DirtyRectsCount);
+      StackArray<RECT, 4> rects(params.DirtyRectsCount);
 
       nsIntRegionRectIterator iter(mInvalidRegion);
       const IntRect* r;
       uint32_t i = 0;
       while ((r = iter.Next()) != nullptr) {
-        RECT rect;
-        rect.left = r->x;
-        rect.top = r->y;
-        rect.bottom = r->YMost();
-        rect.right = r->XMost();
-
-        rects.push_back(rect);
+        rects[i].left = r->x;
+        rects[i].top = r->y;
+        rects[i].bottom = r->YMost();
+        rects[i].right = r->XMost();
+        i++;
       }
 
-      params.pDirtyRects = &rects.front();
+      params.pDirtyRects = rects.data();
       chain->Present1(presentInterval, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0, &params);
     } else {
       mSwapChain->Present(presentInterval, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0);
     }
     mDisableSequenceForNextFrame = false;
     if (mTarget) {
       PaintToTarget();
     }
--- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
+++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
@@ -131,16 +131,19 @@ MacIOSurfaceTextureSourceOGL::BindTextur
   mSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(gl())->GetCGLContext());
   ApplyFilterToBoundTexture(gl(), aFilter, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
 }
 
 void
 MacIOSurfaceTextureSourceOGL::SetCompositor(Compositor* aCompositor)
 {
   mCompositor = static_cast<CompositorOGL*>(aCompositor);
+  if (mNextSibling) {
+    mNextSibling->SetCompositor(aCompositor);
+  }
 }
 
 gl::GLContext*
 MacIOSurfaceTextureSourceOGL::gl() const
 {
   return mCompositor ? mCompositor->gl() : nullptr;
 }
 
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -330,16 +330,19 @@ GLTextureSource::BindTexture(GLenum aTex
   ApplyFilterToBoundTexture(gl(), aFilter, mTextureTarget);
 }
 
 void
 GLTextureSource::SetCompositor(Compositor* aCompositor)
 {
   MOZ_ASSERT(aCompositor);
   mCompositor = static_cast<CompositorOGL*>(aCompositor);
+  if (mNextSibling) {
+    mNextSibling->SetCompositor(aCompositor);
+  }
 }
 
 bool
 GLTextureSource::IsValid() const
 {
   return !!gl() && mTextureHandle != 0;
 }
 
--- a/gfx/src/DriverCrashGuard.cpp
+++ b/gfx/src/DriverCrashGuard.cpp
@@ -152,18 +152,16 @@ DriverCrashGuard::GetGuardFile()
     return nullptr;
   }
   return file;
 }
 
 void
 DriverCrashGuard::ActivateGuard()
 {
-  MOZ_ASSERT(XRE_IsParentProcess());
-
   mGuardActivated = true;
 
 #ifdef MOZ_CRASHREPORTER
   // Anotate crash reports only if we're a real guard. Otherwise, we could
   // attribute a random parent process crash to a graphics problem in a child
   // process.
   if (mMode != Mode::Proxy) {
     CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -446,17 +446,20 @@ DecodePool::GetIOEventTarget()
 void
 DecodePool::Decode(Decoder* aDecoder)
 {
   MOZ_ASSERT(aDecoder);
 
   nsresult rv = aDecoder->Decode();
 
   if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) {
-    if (aDecoder->HasProgress()) {
+    // If this isn't a metadata decode, notify for the progress we've made so
+    // far. It's important that metadata decode results are delivered
+    // atomically, so for those decodes we wait until NotifyDecodeComplete.
+    if (aDecoder->HasProgress() && !aDecoder->IsMetadataDecode()) {
       NotifyProgress(aDecoder);
     }
     // The decoder will ensure that a new worker gets enqueued to continue
     // decoding when more data is available.
   } else {
     NotifyDecodeComplete(aDecoder);
   }
 }
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -32,20 +32,18 @@ Decoder::Decoder(RasterImage* aImage)
   , mFailCode(NS_OK)
   , mChunkCount(0)
   , mFlags(0)
   , mBytesDecoded(0)
   , mInitialized(false)
   , mMetadataDecode(false)
   , mSendPartialInvalidations(false)
   , mImageIsTransient(false)
-  , mImageIsLocked(false)
   , mFirstFrameDecode(false)
   , mInFrame(false)
-  , mIsAnimated(false)
   , mDataDone(false)
   , mDecodeDone(false)
   , mDataError(false)
   , mDecodeAborted(false)
   , mShouldReportError(false)
 { }
 
 Decoder::~Decoder()
@@ -232,17 +230,17 @@ Decoder::CompleteDecode()
   }
 
   if (mDecodeDone && !IsMetadataDecode()) {
     MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
 
     // If this image wasn't animated and isn't a transient image, mark its frame
     // as optimizable. We don't support optimizing animated images and
     // optimizing transient images isn't worth it.
-    if (!mIsAnimated && !mImageIsTransient && mCurrentFrame) {
+    if (!HasAnimation() && !mImageIsTransient && mCurrentFrame) {
       mCurrentFrame->SetOptimizable();
     }
   }
 }
 
 nsresult
 Decoder::AllocateFrame(uint32_t aFrameNum,
                        const nsIntSize& aTargetSize,
@@ -255,17 +253,22 @@ Decoder::AllocateFrame(uint32_t aFrameNu
                                         aPaletteDepth, mCurrentFrame.get());
 
   if (mCurrentFrame) {
     // Gather the raw pointers the decoders will use.
     mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
     mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
 
     if (aFrameNum + 1 == mFrameCount) {
-      PostFrameStart();
+      // If we're past the first frame, PostIsAnimated() should've been called.
+      MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());
+
+      // Update our state to reflect the new frame
+      MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
+      mInFrame = true;
     }
   } else {
     PostDataError();
   }
 
   return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
 }
 
@@ -401,29 +404,21 @@ Decoder::PostSize(int32_t aWidth,
 
 void
 Decoder::PostHasTransparency()
 {
   mProgress |= FLAG_HAS_TRANSPARENCY;
 }
 
 void
-Decoder::PostFrameStart()
+Decoder::PostIsAnimated(int32_t aFirstFrameTimeout)
 {
-  // We shouldn't already be mid-frame
-  MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
-
-  // Update our state to reflect the new frame
-  mInFrame = true;
-
-  // If we just became animated, record that fact.
-  if (mFrameCount > 1) {
-    mIsAnimated = true;
-    mProgress |= FLAG_IS_ANIMATED;
-  }
+  mProgress |= FLAG_IS_ANIMATED;
+  mImageMetadata.SetHasAnimation();
+  mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
 }
 
 void
 Decoder::PostFrameStop(Opacity aFrameOpacity    /* = Opacity::TRANSPARENT */,
                        DisposalMethod aDisposalMethod
                                                 /* = DisposalMethod::KEEP */,
                        int32_t aTimeout         /* = 0 */,
                        BlendMethod aBlendMethod /* = BlendMethod::OVER */)
@@ -437,34 +432,34 @@ Decoder::PostFrameStop(Opacity aFrameOpa
   mInFrame = false;
 
   mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
 
   mProgress |= FLAG_FRAME_COMPLETE;
 
   // If we're not sending partial invalidations, then we send an invalidation
   // here when the first frame is complete.
-  if (!mSendPartialInvalidations && !mIsAnimated) {
+  if (!mSendPartialInvalidations && !HasAnimation()) {
     mInvalidRect.UnionRect(mInvalidRect,
                            gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
   }
 }
 
 void
 Decoder::PostInvalidation(const nsIntRect& aRect,
                           const Maybe<nsIntRect>& aRectAtTargetSize
                             /* = Nothing() */)
 {
   // We should be mid-frame
   MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!");
   MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!");
 
   // Record this invalidation, unless we're not sending partial invalidations
   // or we're past the first frame.
-  if (mSendPartialInvalidations && !mIsAnimated) {
+  if (mSendPartialInvalidations && !HasAnimation()) {
     mInvalidRect.UnionRect(mInvalidRect, aRect);
     mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect));
   }
 }
 
 void
 Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
 {
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -177,30 +177,16 @@ public:
    */
   void SetImageIsTransient(bool aIsTransient)
   {
     MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
     mImageIsTransient = aIsTransient;
   }
 
   /**
-   * Set whether the image is locked for the lifetime of this decoder. We lock
-   * the image during our initial decode to ensure that we don't evict any
-   * surfaces before we realize that the image is animated.
-   */
-  void SetImageIsLocked()
-  {
-    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
-    mImageIsLocked = true;
-  }
-
-  bool ImageIsLocked() const { return mImageIsLocked; }
-
-
-  /**
    * Set whether we should stop decoding after the first frame.
    */
   void SetIsFirstFrameDecode()
   {
     MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
     mFirstFrameDecode = true;
   }
 
@@ -220,17 +206,17 @@ public:
 
   // The number of complete frames we have (ie, not including anything
   // in-progress).
   uint32_t GetCompleteFrameCount() {
     return mInFrame ? mFrameCount - 1 : mFrameCount;
   }
 
   // Did we discover that the image we're decoding is animated?
-  bool HasAnimation() const { return mIsAnimated; }
+  bool HasAnimation() const { return mImageMetadata.HasAnimation(); }
 
   // Error tracking
   bool HasError() const { return HasDataError() || HasDecoderError(); }
   bool HasDataError() const { return mDataError; }
   bool HasDecoderError() const { return NS_FAILED(mFailCode); }
   bool ShouldReportError() const { return mShouldReportError; }
   nsresult GetDecoderError() const { return mFailCode; }
   void PostResizeError() { PostDataError(); }
@@ -339,19 +325,21 @@ protected:
   // possibility that the image has transparency, for example because its header
   // specifies that it has an alpha channel, we fire PostHasTransparency
   // immediately. PostFrameStop's aFrameOpacity argument, on the other hand, is
   // only used internally to ImageLib. Because PostFrameStop isn't delivered
   // until the entire frame has been decoded, decoders may take into account the
   // actual contents of the frame and give a more accurate result.
   void PostHasTransparency();
 
-  // Called by decoders when they begin a frame. Informs the image, sends
-  // notifications, and does internal book-keeping.
-  void PostFrameStart();
+  // Called by decoders if they determine that the image is animated.
+  //
+  // @param aTimeout The time for which the first frame should be shown before
+  //                 we advance to the next frame.
+  void PostIsAnimated(int32_t aFirstFrameTimeout);
 
   // Called by decoders when they end a frame. Informs the image, sends
   // notifications, and does internal book-keeping.
   // Specify whether this frame is opaque as an optimization.
   // For animated images, specify the disposal, blend method and timeout for
   // this frame.
   void PostFrameStop(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
                      DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
@@ -446,20 +434,18 @@ private:
 
   uint32_t mFlags;
   size_t mBytesDecoded;
 
   bool mInitialized : 1;
   bool mMetadataDecode : 1;
   bool mSendPartialInvalidations : 1;
   bool mImageIsTransient : 1;
-  bool mImageIsLocked : 1;
   bool mFirstFrameDecode : 1;
   bool mInFrame : 1;
-  bool mIsAnimated : 1;
   bool mDataDone : 1;
   bool mDecodeDone : 1;
   bool mDataError : 1;
   bool mDecodeAborted : 1;
   bool mShouldReportError : 1;
 };
 
 } // namespace image
--- a/image/DecoderFactory.cpp
+++ b/image/DecoderFactory.cpp
@@ -108,38 +108,34 @@ DecoderFactory::GetDecoder(DecoderType a
 DecoderFactory::CreateDecoder(DecoderType aType,
                               RasterImage* aImage,
                               SourceBuffer* aSourceBuffer,
                               const Maybe<IntSize>& aTargetSize,
                               uint32_t aFlags,
                               int aSampleSize,
                               const IntSize& aResolution,
                               bool aIsRedecode,
-                              bool aImageIsTransient,
-                              bool aImageIsLocked)
+                              bool aImageIsTransient)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
 
   nsRefPtr<Decoder> decoder = GetDecoder(aType, aImage, aIsRedecode);
   MOZ_ASSERT(decoder, "Should have a decoder now");
 
   // Initialize the decoder.
   decoder->SetMetadataDecode(false);
   decoder->SetIterator(aSourceBuffer->Iterator());
   decoder->SetFlags(aFlags);
   decoder->SetSampleSize(aSampleSize);
   decoder->SetResolution(aResolution);
   decoder->SetSendPartialInvalidations(!aIsRedecode);
   decoder->SetImageIsTransient(aImageIsTransient);
-
-  if (aImageIsLocked) {
-    decoder->SetImageIsLocked();
-  }
+  decoder->SetIsFirstFrameDecode();
 
   // Set a target size for downscale-during-decode if applicable.
   if (aTargetSize) {
     DebugOnly<nsresult> rv = decoder->SetTargetSize(*aTargetSize);
     MOZ_ASSERT(nsresult(rv) != NS_ERROR_NOT_AVAILABLE,
                "We're downscale-during-decode but decoder doesn't support it?");
     MOZ_ASSERT(NS_SUCCEEDED(rv), "Bad downscale-during-decode target size?");
   }
@@ -148,16 +144,49 @@ DecoderFactory::CreateDecoder(DecoderTyp
   if (NS_FAILED(decoder->GetDecoderError())) {
     return nullptr;
   }
 
   return decoder.forget();
 }
 
 /* static */ already_AddRefed<Decoder>
+DecoderFactory::CreateAnimationDecoder(DecoderType aType,
+                                       RasterImage* aImage,
+                                       SourceBuffer* aSourceBuffer,
+                                       uint32_t aFlags,
+                                       const IntSize& aResolution)
+{
+  if (aType == DecoderType::UNKNOWN) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
+             "Calling CreateAnimationDecoder for non-animating DecoderType");
+
+  nsRefPtr<Decoder> decoder =
+    GetDecoder(aType, aImage, /* aIsRedecode = */ true);
+  MOZ_ASSERT(decoder, "Should have a decoder now");
+
+  // Initialize the decoder.
+  decoder->SetMetadataDecode(false);
+  decoder->SetIterator(aSourceBuffer->Iterator());
+  decoder->SetFlags(aFlags);
+  decoder->SetResolution(aResolution);
+  decoder->SetSendPartialInvalidations(false);
+
+  decoder->Init();
+  if (NS_FAILED(decoder->GetDecoderError())) {
+    return nullptr;
+  }
+
+  return decoder.forget();
+}
+
+/* static */ already_AddRefed<Decoder>
 DecoderFactory::CreateMetadataDecoder(DecoderType aType,
                                       RasterImage* aImage,
                                       SourceBuffer* aSourceBuffer,
                                       int aSampleSize,
                                       const IntSize& aResolution)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
--- a/image/DecoderFactory.h
+++ b/image/DecoderFactory.h
@@ -33,22 +33,23 @@ enum class DecoderType
 
 class DecoderFactory
 {
 public:
   /// @return the type of decoder which is appropriate for @aMimeType.
   static DecoderType GetDecoderType(const char* aMimeType);
 
   /**
-   * Creates and initializes a decoder of type @aType. The decoder will send
-   * notifications to @aImage.
+   * Creates and initializes a decoder for non-animated images of type @aType.
+   * (If the image *is* animated, only the first frame will be decoded.) The
+   * decoder will send notifications to @aImage.
    *
-   * XXX(seth): @aIsRedecode, @aImageIsTransient, and @aImageIsLocked should
-   * really be part of @aFlags. This requires changes to the way that decoder
-   * flags work, though. See bug 1185800.
+   * XXX(seth): @aIsRedecode and @aImageIsTransient should really be part of
+   * @aFlags. This requires changes to the way that decoder flags work, though.
+   * See bug 1185800.
    *
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
    * @param aImage The image will own the decoder and which should receive
    *               notifications as decoding progresses.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
    *                      from.
    * @param aTargetSize If not Nothing(), the target size which the image should
    *                    be scaled to during decoding. It's an error to specify
@@ -57,31 +58,48 @@ public:
    * @param aFlags Flags specifying what type of output the decoder should
    *               produce; see GetDecodeFlags() in RasterImage.h.
    * @param aSampleSize The sample size requested using #-moz-samplesize (or 0
    *                    if none).
    * @param aResolution The resolution requested using #-moz-resolution (or an
    *                    empty rect if none).
    * @param aIsRedecode Specify 'true' if this image has been decoded before.
    * @param aImageIsTransient Specify 'true' if this image is transient.
-   * @param aImageIsLocked Specify 'true' if this image is locked for the
-   *                       lifetime of this decoder, and should be unlocked
-   *                       when the decoder finishes.
    */
   static already_AddRefed<Decoder>
   CreateDecoder(DecoderType aType,
                 RasterImage* aImage,
                 SourceBuffer* aSourceBuffer,
                 const Maybe<gfx::IntSize>& aTargetSize,
                 uint32_t aFlags,
                 int aSampleSize,
                 const gfx::IntSize& aResolution,
                 bool aIsRedecode,
-                bool aImageIsTransient,
-                bool aImageIsLocked);
+                bool aImageIsTransient);
+
+  /**
+   * Creates and initializes a decoder for animated images of type @aType.
+   * The decoder will send notifications to @aImage.
+   *
+   * @param aType Which type of decoder to create - JPEG, PNG, etc.
+   * @param aImage The image will own the decoder and which should receive
+   *               notifications as decoding progresses.
+   * @param aSourceBuffer The SourceBuffer which the decoder will read its data
+   *                      from.
+   * @param aFlags Flags specifying what type of output the decoder should
+   *               produce; see GetDecodeFlags() in RasterImage.h.
+   * @param aResolution The resolution requested using #-moz-resolution (or an
+   *                    empty rect if none).
+   */
+  static already_AddRefed<Decoder>
+  CreateAnimationDecoder(DecoderType aType,
+                         RasterImage* aImage,
+                         SourceBuffer* aSourceBuffer,
+                         uint32_t aFlags,
+                         const gfx::IntSize& aResolution);
 
   /**
    * Creates and initializes a metadata decoder of type @aType. This decoder
    * will only decode the image's header, extracting metadata like the size of
    * the image. No actual image data will be decoded and no surfaces will be
    * allocated. The decoder will send notifications to @aImage.
    *
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -286,42 +286,47 @@ FrameAnimator::GetCompositedFrame(uint32
   MOZ_ASSERT(!result || !result.DrawableRef()->GetIsPaletted(),
              "About to return a paletted frame");
   return result;
 }
 
 int32_t
 FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
 {
+  int32_t rawTimeout = 0;
+
   RawAccessFrameRef frame = GetRawFrame(aFrameNum);
-  if (!frame) {
+  if (frame) {
+    AnimationData data = frame->GetAnimationData();
+    rawTimeout = data.mRawTimeout;
+  } else if (aFrameNum == 0) {
+    rawTimeout = mFirstFrameTimeout;
+  } else {
     NS_WARNING("No frame; called GetTimeoutForFrame too early?");
     return 100;
   }
 
-  AnimationData data = frame->GetAnimationData();
-
   // Ensure a minimal time between updates so we don't throttle the UI thread.
   // consider 0 == unspecified and make it fast but not too fast.  Unless we
   // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
   // 207059. The behavior of recent IE and Opera versions seems to be:
   // IE 6/Win:
   //   10 - 50ms go 100ms
   //   >50ms go correct speed
   // Opera 7 final/Win:
   //   10ms goes 100ms
   //   >10ms go correct speed
   // It seems that there are broken tools out there that set a 0ms or 10ms
   // timeout when they really want a "default" one.  So munge values in that
   // range.
-  if (data.mRawTimeout >= 0 && data.mRawTimeout <= 10) {
+  if (rawTimeout >= 0 && rawTimeout <= 10) {
     return 100;
   }
 
-  return data.mRawTimeout;
+  return rawTimeout;
 }
 
 static void
 DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
                                    SurfaceMemoryCounterType aType,
                                    nsTArray<SurfaceMemoryCounter>& aCounters,
                                    MallocSizeOf aMallocSizeOf)
 {
--- a/image/FrameAnimator.h
+++ b/image/FrameAnimator.h
@@ -28,16 +28,17 @@ public:
                 gfx::IntSize aSize,
                 uint16_t aAnimationMode)
     : mImage(aImage)
     , mSize(aSize)
     , mCurrentAnimationFrameIndex(0)
     , mLoopRemainingCount(-1)
     , mLastCompositedFrameIndex(-1)
     , mLoopCount(-1)
+    , mFirstFrameTimeout(0)
     , mAnimationMode(aAnimationMode)
     , mDoneDecoding(false)
   { }
 
   /**
    * Return value from RequestRefresh. Tells callers what happened in that call
    * to RequestRefresh.
    */
@@ -143,16 +144,22 @@ public:
 
   /*
    * Set number of times to loop the image.
    * @note -1 means loop forever.
    */
   void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
   int32_t LoopCount() const { return mLoopCount; }
 
+  /*
+   * Set the timeout for the first frame. This is used to allow animation
+   * scheduling even before a full decode runs for this image.
+   */
+  void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; }
+
   /**
    * Collect an accounting of the memory occupied by the compositing surfaces we
    * use during animation playback. All of the actual animation frames are
    * stored in the SurfaceCache, so we don't need to report them here.
    */
   void CollectSizeOfCompositingSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                         MallocSizeOf aMallocSizeOf) const;
 
@@ -272,16 +279,19 @@ private: // data
   int32_t mLoopRemainingCount;
 
   //! Track the last composited frame for Optimizations (See DoComposite code)
   int32_t mLastCompositedFrameIndex;
 
   //! The total number of loops for the image.
   int32_t mLoopCount;
 
+  //! The timeout for the first frame of this image.
+  int32_t mFirstFrameTimeout;
+
   //! The animation mode of this image. Constants defined in imgIContainer.
   uint16_t mAnimationMode;
 
   //! Whether this image is done being decoded.
   bool mDoneDecoding;
 };
 
 } // namespace image
deleted file mode 100644
--- a/image/ImageMetadata.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "ImageMetadata.h"
-
-#include "RasterImage.h"
-#include "nsComponentManagerUtils.h"
-#include "nsISupportsPrimitives.h"
-#include "nsXPCOMCID.h"
-
-namespace mozilla {
-namespace image {
-
-nsresult
-ImageMetadata::SetOnImage(RasterImage* aImage)
-{
-  nsresult rv = NS_OK;
-
-  if (mHotspotX != -1 && mHotspotY != -1) {
-    nsCOMPtr<nsISupportsPRUint32> intwrapx =
-      do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
-    nsCOMPtr<nsISupportsPRUint32> intwrapy =
-      do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
-    intwrapx->SetData(mHotspotX);
-    intwrapy->SetData(mHotspotY);
-    aImage->Set("hotspotX", intwrapx);
-    aImage->Set("hotspotY", intwrapy);
-  }
-
-  aImage->SetLoopCount(mLoopCount);
-
-  if (HasSize()) {
-    MOZ_ASSERT(HasOrientation(), "Should have orientation");
-    rv = aImage->SetSize(GetWidth(), GetHeight(), GetOrientation());
-  }
-
-  return rv;
-}
-
-} // namespace image
-} // namespace mozilla
--- a/image/ImageMetadata.h
+++ b/image/ImageMetadata.h
@@ -17,58 +17,64 @@ namespace image {
 
 class RasterImage;
 
 // The metadata about an image that decoders accumulate as they decode.
 class ImageMetadata
 {
 public:
   ImageMetadata()
-    : mHotspotX(-1)
-    , mHotspotY(-1)
-    , mLoopCount(-1)
+    : mLoopCount(-1)
+    , mFirstFrameTimeout(0)
+    , mHasAnimation(false)
   { }
 
-  // Set the metadata this object represents on an image.
-  nsresult SetOnImage(RasterImage* aImage);
+  void SetHotspot(uint16_t aHotspotX, uint16_t aHotspotY)
+  {
+    mHotspot = Some(gfx::IntPoint(aHotspotX, aHotspotY));
+  }
+  gfx::IntPoint GetHotspot() const { return *mHotspot; }
+  bool HasHotspot() const { return mHotspot.isSome(); }
 
-  void SetHotspot(uint16_t hotspotx, uint16_t hotspoty)
-  {
-    mHotspotX = hotspotx;
-    mHotspotY = hotspoty;
-  }
   void SetLoopCount(int32_t loopcount)
   {
     mLoopCount = loopcount;
   }
+  int32_t GetLoopCount() const { return mLoopCount; }
+
+  void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; }
+  int32_t GetFirstFrameTimeout() const { return mFirstFrameTimeout; }
 
   void SetSize(int32_t width, int32_t height, Orientation orientation)
   {
     if (!HasSize()) {
       mSize.emplace(nsIntSize(width, height));
       mOrientation.emplace(orientation);
     }
   }
-
+  nsIntSize GetSize() const { return *mSize; }
+  Orientation GetOrientation() const { return *mOrientation; }
   bool HasSize() const { return mSize.isSome(); }
   bool HasOrientation() const { return mOrientation.isSome(); }
 
-  int32_t GetWidth() const { return mSize->width; }
-  int32_t GetHeight() const { return mSize->height; }
-  nsIntSize GetSize() const { return *mSize; }
-  Orientation GetOrientation() const { return *mOrientation; }
+  void SetHasAnimation() { mHasAnimation = true; }
+  bool HasAnimation() const { return mHasAnimation; }
 
 private:
-  // The hotspot found on cursors, or -1 if none was found.
-  int32_t mHotspotX;
-  int32_t mHotspotY;
+  /// The hotspot found on cursors, if present.
+  Maybe<gfx::IntPoint> mHotspot;
 
-  // The loop count for animated images, or -1 for infinite loop.
+  /// The loop count for animated images, or -1 for infinite loop.
   int32_t mLoopCount;
 
+  /// The timeout of an animated image's first frame.
+  int32_t mFirstFrameTimeout;
+
   Maybe<nsIntSize> mSize;
   Maybe<Orientation> mOrientation;
+
+  bool mHasAnimation : 1;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_ImageMetadata_h
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -19,16 +19,17 @@
 #include "prsystem.h"
 #include "ImageContainer.h"
 #include "ImageRegion.h"
 #include "Layers.h"
 #include "LookupResult.h"
 #include "nsIConsoleService.h"
 #include "nsIInputStream.h"
 #include "nsIScriptError.h"
+#include "nsISupportsPrimitives.h"
 #include "nsPresContext.h"
 #include "SourceBuffer.h"
 #include "SurfaceCache.h"
 #include "FrameAnimator.h"
 
 #include "gfxContext.h"
 
 #include "mozilla/gfx/2D.h"
@@ -473,17 +474,17 @@ RasterImage::LookupFrame(uint32_t aFrame
   }
 
   if (result.Type() == MatchType::NOT_FOUND ||
       result.Type() == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND ||
       ((aFlags & FLAG_SYNC_DECODE) && !result)) {
     // We don't have a copy of this frame, and there's no decoder working on
     // one. (Or we're sync decoding and the existing decoder hasn't even started
     // yet.) Trigger decoding so it'll be available next time.
-    MOZ_ASSERT(!mAnim, "Animated frames should be locked");
+    MOZ_ASSERT(!mAnim || GetNumFrames() < 1, "Animated frames should be locked");
 
     Decode(requestedSize, aFlags);
 
     // If we can sync decode, we should already have the frame.
     if (aFlags & FLAG_SYNC_DECODE) {
       result = LookupFrameInternal(aFrameNum, requestedSize, aFlags);
     }
   }
@@ -524,17 +525,17 @@ uint32_t
 RasterImage::GetRequestedFrameIndex(uint32_t aWhichFrame) const
 {
   return aWhichFrame == FRAME_FIRST ? 0 : GetCurrentFrameIndex();
 }
 
 IntRect
 RasterImage::GetFirstFrameRect()
 {
-  if (mAnim) {
+  if (mAnim && mHasBeenDecoded) {
     return mAnim->GetFirstFrameRefreshArea();
   }
 
   // Fall back to our size. This is implicitly zero-size if !mHasSize.
   return IntRect(IntPoint(0,0), mSize);
 }
 
 NS_IMETHODIMP_(bool)
@@ -577,17 +578,20 @@ RasterImage::GetAnimated(bool* aAnimated
 
   // If we have mAnim, we can know for sure
   if (mAnim) {
     *aAnimated = true;
     return NS_OK;
   }
 
   // Otherwise, we need to have been decoded to know for sure, since if we were
-  // decoded at least once mAnim would have been created for animated images
+  // decoded at least once mAnim would have been created for animated images.
+  // This is true even though we check for animation during the metadata decode,
+  // because we may still discover animation only during the full decode for
+  // corrupt images.
   if (!mHasBeenDecoded) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // We know for sure
   *aAnimated = false;
 
   return NS_OK;
@@ -916,70 +920,97 @@ RasterImage::OnAddedFrame(uint32_t aNewF
   if (mError) {
     return;  // We're in an error state, possibly due to OOM. Bail.
   }
 
   if (aNewFrameCount > mFrameCount) {
     mFrameCount = aNewFrameCount;
 
     if (aNewFrameCount == 2) {
-      // We're becoming animated, so initialize animation stuff.
-      MOZ_ASSERT(!mAnim, "Already have animation state?");
-      mAnim = MakeUnique<FrameAnimator>(this, mSize, mAnimationMode);
+      MOZ_ASSERT(mAnim, "Should already have animation state");
 
-      // We don't support discarding animated images (See bug 414259).
-      // Lock the image and throw away the key.
-      //
-      // Note that this is inefficient, since we could get rid of the source
-      // data too. However, doing this is actually hard, because we're probably
-      // mid-decode, and thus we're decoding out of the source buffer. Since
-      // we're going to fix this anyway later, and since we didn't kill the
-      // source data in the old world either, locking is acceptable for the
-      // moment.
-      LockImage();
-
+      // We may be able to start animating.
       if (mPendingAnimation && ShouldAnimate()) {
         StartAnimation();
       }
     }
     if (aNewFrameCount > 1) {
       mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea);
     }
   }
 }
 
 nsresult
-RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation)
+RasterImage::SetMetadata(const ImageMetadata& aMetadata,
+                         bool aFromMetadataDecode)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
     return NS_ERROR_FAILURE;
   }
 
-  // Ensure that we have positive values
-  // XXX - Why isn't the size unsigned? Should this be changed?
-  if ((aWidth < 0) || (aHeight < 0)) {
-    return NS_ERROR_INVALID_ARG;
+  if (aMetadata.HasSize()) {
+    IntSize size = aMetadata.GetSize();
+    if (size.width < 0 || size.height < 0) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    MOZ_ASSERT(aMetadata.HasOrientation());
+    Orientation orientation = aMetadata.GetOrientation();
+
+    // If we already have a size, check the new size against the old one.
+    if (mHasSize && (size != mSize || orientation != mOrientation)) {
+      NS_WARNING("Image changed size or orientation on redecode! "
+                 "This should not happen!");
+      DoError();
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    // Set the size and flag that we have it.
+    mSize = size;
+    mOrientation = orientation;
+    mHasSize = true;
   }
 
-  // if we already have a size, check the new size against the old one
-  if (mHasSize &&
-      ((aWidth != mSize.width) ||
-       (aHeight != mSize.height) ||
-       (aOrientation != mOrientation))) {
-    NS_WARNING("Image changed size on redecode! This should not happen!");
-    DoError();
-    return NS_ERROR_UNEXPECTED;
+  if (mHasSize && aMetadata.HasAnimation() && !mAnim) {
+    // We're becoming animated, so initialize animation stuff.
+    mAnim = MakeUnique<FrameAnimator>(this, mSize, mAnimationMode);
+
+    // We don't support discarding animated images (See bug 414259).
+    // Lock the image and throw away the key.
+    LockImage();
+
+    if (!aFromMetadataDecode) {
+      // The metadata decode reported that this image isn't animated, but we
+      // discovered that it actually was during the full decode. This is a
+      // rare failure that only occurs for corrupt images. To recover, we need
+      // to discard all existing surfaces and redecode.
+      RecoverFromLossOfFrames(mSize, DECODE_FLAGS_DEFAULT);
+    }
   }
 
-  // Set the size and flag that we have it
-  mSize.SizeTo(aWidth, aHeight);
-  mOrientation = aOrientation;
-  mHasSize = true;
+  if (mAnim) {
+    mAnim->SetLoopCount(aMetadata.GetLoopCount());
+    mAnim->SetFirstFrameTimeout(aMetadata.GetFirstFrameTimeout());
+  }
+
+  if (aMetadata.HasHotspot()) {
+    IntPoint hotspot = aMetadata.GetHotspot();
+
+    nsCOMPtr<nsISupportsPRUint32> intwrapx =
+      do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
+    nsCOMPtr<nsISupportsPRUint32> intwrapy =
+      do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
+    intwrapx->SetData(hotspot.x);
+    intwrapy->SetData(hotspot.y);
+
+    Set("hotspotX", intwrapx);
+    Set("hotspotY", intwrapy);
+  }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RasterImage::SetAnimationMode(uint16_t aAnimationMode)
 {
   if (mAnim) {
@@ -994,20 +1025,19 @@ nsresult
 RasterImage::StartAnimation()
 {
   if (mError) {
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(ShouldAnimate(), "Should not animate!");
 
-  // If we don't have mAnim yet, then we're not ready to animate.  Setting
-  // mPendingAnimation will cause us to start animating as soon as we have a
-  // second frame, which causes mAnim to be constructed.
-  mPendingAnimation = !mAnim;
+  // If we're not ready to animate, then set mPendingAnimation, which will cause
+  // us to start animating if and when we do become ready.
+  mPendingAnimation = !mAnim || GetNumFrames() < 2;
   if (mPendingAnimation) {
     return NS_OK;
   }
 
   // A timeout of -1 means we should display this frame forever.
   if (mAnim->GetTimeoutForFrame(GetCurrentFrameIndex()) < 0) {
     mAnimationFinished = true;
     return NS_ERROR_ABORT;
@@ -1086,29 +1116,16 @@ NS_IMETHODIMP_(float)
 RasterImage::GetFrameIndex(uint32_t aWhichFrame)
 {
   MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument");
   return (aWhichFrame == FRAME_FIRST || !mAnim)
          ? 0.0f
          : mAnim->GetCurrentAnimationFrameIndex();
 }
 
-void
-RasterImage::SetLoopCount(int32_t aLoopCount)
-{
-  if (mError) {
-    return;
-  }
-
-  // No need to set this if we're not an animation.
-  if (mAnim) {
-    mAnim->SetLoopCount(aLoopCount);
-  }
-}
-
 NS_IMETHODIMP_(IntRect)
 RasterImage::GetImageSpaceInvalidationRect(const IntRect& aRect)
 {
   return aRect;
 }
 
 nsresult
 RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus,
@@ -1386,31 +1403,30 @@ RasterImage::Decode(const IntSize& aSize
   }
 
   MOZ_ASSERT(mDownscaleDuringDecode || aSize == mSize,
              "Can only decode to our intrinsic size if we're not allowed to "
              "downscale-during-decode");
 
   Maybe<IntSize> targetSize = mSize != aSize ? Some(aSize) : Nothing();
 
-  bool imageIsLocked = false;
-  if (!mHasBeenDecoded) {
-    // Lock the image while we're decoding, so that it doesn't get evicted from
-    // the SurfaceCache before we have a chance to realize that it's animated.
-    // The corresponding unlock happens in FinalizeDecoder.
-    LockImage();
-    imageIsLocked = true;
+  // Create a decoder.
+  nsRefPtr<Decoder> decoder;
+  if (mAnim) {
+    decoder = DecoderFactory::CreateAnimationDecoder(mDecoderType, this,
+                                                     mSourceBuffer, aFlags,
+                                                     mRequestedResolution);
+  } else {
+    decoder = DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer,
+                                            targetSize, aFlags,
+                                            mRequestedSampleSize,
+                                            mRequestedResolution,
+                                            mHasBeenDecoded, mTransient);
   }
 
-  // Create a decoder.
-  nsRefPtr<Decoder> decoder =
-    DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer, targetSize,
-                                  aFlags, mRequestedSampleSize, mRequestedResolution,
-                                  mHasBeenDecoded, mTransient, imageIsLocked);
-
   // Make sure DecoderFactory was able to create a decoder successfully.
   if (!decoder) {
     return NS_ERROR_FAILURE;
   }
 
   // Add a placeholder for the first frame to the SurfaceCache so we won't
   // trigger any more decoders with the same parameters.
   InsertOutcome outcome =
@@ -1478,16 +1494,21 @@ RasterImage::RecoverFromLossOfFrames(con
     return;
   }
 
   NS_WARNING("An imgFrame became invalid. Attempting to recover...");
 
   // Discard all existing frames, since they're probably all now invalid.
   SurfaceCache::RemoveImage(ImageKey(this));
 
+  // Relock the image if it's supposed to be locked.
+  if (mLockCount > 0) {
+    SurfaceCache::LockImage(ImageKey(this));
+  }
+
   // Animated images require some special handling, because we normally require
   // that they never be discarded.
   if (mAnim) {
     Decode(mSize, aFlags | FLAG_SYNC_DECODE);
     ResetAnimation();
     return;
   }
 
@@ -1943,38 +1964,30 @@ RasterImage::FinalizeDecoder(Decoder* aD
              "Finalizing a decoder in the middle of a frame");
 
   // If the decoder detected an error, log it to the error console.
   if (aDecoder->ShouldReportError() && !aDecoder->WasAborted()) {
     ReportDecoderError(aDecoder);
   }
 
   // Record all the metadata the decoder gathered about this image.
-  nsresult rv = aDecoder->GetImageMetadata().SetOnImage(this);
+  nsresult rv = SetMetadata(aDecoder->GetImageMetadata(),
+                            aDecoder->IsMetadataDecode());
   if (NS_FAILED(rv)) {
     aDecoder->PostResizeError();
   }
 
   MOZ_ASSERT(mError || mHasSize || !aDecoder->HasSize(),
              "Should have handed off size by now");
 
   if (aDecoder->GetDecodeTotallyDone() && !mError) {
     // Flag that we've been decoded before.
     mHasBeenDecoded = true;
-
-    if (aDecoder->HasAnimation()) {
-      if (mAnim) {
-        mAnim->SetDoneDecoding(true);
-      } else {
-        // The OnAddedFrame event that will create mAnim is still in the event
-        // queue. Wait for it.
-        nsCOMPtr<nsIRunnable> runnable =
-          NS_NewRunnableMethod(this, &RasterImage::MarkAnimationDecoded);
-        NS_DispatchToMainThread(runnable);
-      }
+    if (mAnim) {
+      mAnim->SetDoneDecoding(true);
     }
   }
 
   // Send out any final notifications.
   NotifyProgress(aDecoder->TakeProgress(),
                  aDecoder->TakeInvalidRect(),
                  aDecoder->GetDecodeFlags());
 
@@ -2012,40 +2025,24 @@ RasterImage::FinalizeDecoder(Decoder* aD
     // If we were waiting to fire the load event, go ahead and fire it now.
     if (mLoadProgress && wasMetadata) {
       NotifyForLoadEvent(*mLoadProgress);
       mLoadProgress = Nothing();
       NotifyProgress(FLAG_ONLOAD_UNBLOCKED);
     }
   }
 
-  if (aDecoder->ImageIsLocked()) {
-    // Unlock the image, balancing the LockImage call we made in CreateDecoder.
-    UnlockImage();
-  }
-
   // If we were a metadata decode and a full decode was requested, do it.
   if (done && wasMetadata && mWantFullDecode) {
     mWantFullDecode = false;
     RequestDecode();
   }
 }
 
 void
-RasterImage::MarkAnimationDecoded()
-{
-  MOZ_ASSERT(mAnim, "Should have an animation now");
-  if (!mAnim) {
-    return;
-  }
-
-  mAnim->SetDoneDecoding(true);
-}
-
-void
 RasterImage::ReportDecoderError(Decoder* aDecoder)
 {
   nsCOMPtr<nsIConsoleService> consoleService =
     do_GetService(NS_CONSOLESERVICE_CONTRACTID);
   nsCOMPtr<nsIScriptError> errorObject =
     do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
 
   if (consoleService && errorObject && !aDecoder->HasDecoderError()) {
--- a/image/RasterImage.h
+++ b/image/RasterImage.h
@@ -127,16 +127,17 @@ namespace layers {
 class ImageContainer;
 class Image;
 } // namespace layers
 
 namespace image {
 
 class Decoder;
 class FrameAnimator;
+class ImageMetadata;
 class SourceBuffer;
 
 /**
  * Given a set of imgIContainer FLAG_* flags, returns those flags that can
  * affect the output of decoders.
  */
 inline MOZ_CONSTEXPR uint32_t
 DecodeFlags(uint32_t aFlags)
@@ -183,28 +184,16 @@ public:
 
 
   //////////////////////////////////////////////////////////////////////////////
   // Decoder callbacks.
   //////////////////////////////////////////////////////////////////////////////
 
   void OnAddedFrame(uint32_t aNewFrameCount, const nsIntRect& aNewRefreshArea);
 
-  /** Sets the size and inherent orientation of the container. This should only
-   * be called by the decoder. This function may be called multiple times, but
-   * will throw an error if subsequent calls do not match the first.
-   */
-  nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation);
-
-  /**
-   * Number of times to loop the image.
-   * @note -1 means forever.
-   */
-  void     SetLoopCount(int32_t aLoopCount);
-
   /**
    * Sends the provided progress notifications to ProgressTracker.
    *
    * Main-thread only.
    *
    * @param aProgress    The progress notifications to send.
    * @param aInvalidRect An invalidation rect to send.
    * @param aFlags       The decode flags used by the decoder that generated
@@ -217,18 +206,17 @@ public:
 
   /**
    * Records telemetry and does final teardown of the provided decoder.
    *
    * Main-thread only.
    */
   void FinalizeDecoder(Decoder* aDecoder);
 
-  // Helper methods for FinalizeDecoder.
-  void MarkAnimationDecoded();
+  // Helper method for FinalizeDecoder.
   void ReportDecoderError(Decoder* aDecoder);
 
 
   //////////////////////////////////////////////////////////////////////////////
   // Network callbacks.
   //////////////////////////////////////////////////////////////////////////////
 
   virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
@@ -335,16 +323,29 @@ private:
 
   /**
    * Creates and runs a metadata decoder, either synchronously or
    * asynchronously according to @aFlags.
    */
   NS_IMETHOD DecodeMetadata(uint32_t aFlags);
 
   /**
+   * Sets the size, inherent orientation, animation metadata, and other
+   * information about the image gathered during decoding.
+   *
+   * This function may be called multiple times, but will throw an error if
+   * subsequent calls do not match the first.
+   *
+   * @param aMetadata The metadata to set on this image.
+   * @param aFromMetadataDecode True if this metadata came from a metadata
+   *                            decode; false if it came from a full decode.
+   */
+  nsresult SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode);
+
+  /**
    * In catastrophic circumstances like a GPU driver crash, we may lose our
    * frames even if they're locked. RecoverFromLossOfFrames discards all
    * existing frames and redecodes using the provided @aSize and @aFlags.
    */
   void RecoverFromLossOfFrames(const nsIntSize& aSize, uint32_t aFlags);
 
 private: // data
   nsIntSize                  mSize;
--- a/image/decoders/icon/mac/nsIconChannelCocoa.mm
+++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm
@@ -220,16 +220,20 @@ nsIconChannel::ExtractIconInfoFromUrl(ns
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsIconChannel::AsyncOpen(nsIStreamListener* aListener,
                                        nsISupports* ctxt)
 {
+  MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+             mLoadInfo->GetInitialSecurityCheckDone(),
+             "security flags in loadInfo but asyncOpen2() not called");
+
   nsCOMPtr<nsIInputStream> inStream;
   nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Init our stream pump
   rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/image/decoders/icon/win/nsIconChannel.cpp
+++ b/image/decoders/icon/win/nsIconChannel.cpp
@@ -234,16 +234,20 @@ nsIconChannel::ExtractIconInfoFromUrl(ns
 
   return file->Clone(aLocalFile);
 }
 
 NS_IMETHODIMP
 nsIconChannel::AsyncOpen(nsIStreamListener* aListener,
                                        nsISupports* ctxt)
 {
+  MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+             mLoadInfo->GetInitialSecurityCheckDone(),
+             "security flags in loadInfo but asyncOpen2() not called");
+
   nsCOMPtr<nsIInputStream> inStream;
   nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Init our streampump
   rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -852,16 +852,21 @@ nsGIFDecoder2::WriteInternal(const char*
             method == DisposalMethod::CLEAR) {
           // We may have to display the background under this image during
           // animation playback, so we regard it as transparent.
           PostHasTransparency();
         }
       }
 
       mGIFStruct.delay_time = GETINT16(q + 1) * 10;
+
+      if (mGIFStruct.delay_time > 0) {
+        PostIsAnimated(mGIFStruct.delay_time);
+      }
+
       GETN(1, gif_consume_block);
       break;
 
     case gif_comment_extension:
       if (*q) {
         GETN(*q, gif_consume_comment);
       } else {
         GETN(1, gif_image_start);
@@ -916,21 +921,30 @@ nsGIFDecoder2::WriteInternal(const char*
 
         default:
           // 0,3-7 are yet to be defined netscape extension codes
           mGIFStruct.state = gif_error;
       }
       break;
 
     case gif_image_header: {
-      if (mGIFStruct.images_decoded > 0 && IsFirstFrameDecode()) {
-        // We're about to get a second frame, but we only want the first. Stop
-        // decoding now.
-        mGIFStruct.state = gif_done;
-        break;
+      if (mGIFStruct.images_decoded == 1) {
+        if (!HasAnimation()) {
+          // We should've already called PostIsAnimated(); this must be a
+          // corrupt animated image with a first frame timeout of zero. Signal
+          // that we're animated now, before the first-frame decode early exit
+          // below, so that RasterImage can detect that this happened.
+          PostIsAnimated(/* aFirstFrameTimeout = */ 0);
+        }
+        if (IsFirstFrameDecode()) {
+          // We're about to get a second frame, but we only want the first. Stop
+          // decoding now.
+          mGIFStruct.state = gif_done;
+          break;
+        }
       }
 
       // Get image offsets, with respect to the screen origin
       mGIFStruct.x_offset = GETINT16(q);
       mGIFStruct.y_offset = GETINT16(q + 2);
 
       // Get image width and height.
       mGIFStruct.width  = GETINT16(q + 4);
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -373,18 +373,18 @@ nsICODecoder::WriteInternal(const char* 
 
   // If we have a PNG, let the PNG decoder do all of the rest of the work
   if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
     if (!WriteToContainedDecoder(aBuffer, aCount)) {
       return;
     }
 
     if (!HasSize() && mContainedDecoder->HasSize()) {
-      PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
-               mContainedDecoder->GetImageMetadata().GetHeight());
+      nsIntSize size = mContainedDecoder->GetSize();
+      PostSize(size.width, size.height);
     }
 
     mPos += aCount;
     aBuffer += aCount;
     aCount = 0;
 
     // Raymond Chen says that 32bpp only are valid PNG ICOs
     // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
@@ -474,18 +474,18 @@ nsICODecoder::WriteInternal(const char* 
       return;
     }
 
     // Write out the BMP's bitmap info header
     if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
       return;
     }
 
-    PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
-             mContainedDecoder->GetImageMetadata().GetHeight());
+    nsIntSize size = mContainedDecoder->GetSize();
+    PostSize(size.width, size.height);
 
     // We have the size. If we're doing a metadata decode, we're done.
     if (IsMetadataDecode()) {
       return;
     }
 
     // Sometimes the ICO BPP header field is not filled out
     // so we should trust the contained resource over our own
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -51,56 +51,59 @@ GetPNGDecoderAccountingLog()
 
 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
  : mDispose(DisposalMethod::KEEP)
  , mBlend(BlendMethod::OVER)
  , mTimeout(0)
 { }
 
 #ifdef PNG_APNG_SUPPORTED
+
+int32_t GetNextFrameDelay(png_structp aPNG, png_infop aInfo)
+{
+  // Delay, in seconds, is delayNum / delayDen.
+  png_uint_16 delayNum = png_get_next_frame_delay_num(aPNG, aInfo);
+  png_uint_16 delayDen = png_get_next_frame_delay_den(aPNG, aInfo);
+
+  if (delayNum == 0) {
+    return 0; // SetFrameTimeout() will set to a minimum.
+  }
+
+  if (delayDen == 0) {
+    delayDen = 100; // So says the APNG spec.
+  }
+
+  // Need to cast delay_num to float to have a proper division and
+  // the result to int to avoid a compiler warning.
+  return static_cast<int32_t>(static_cast<double>(delayNum) * 1000 / delayDen);
+}
+
 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
  : mDispose(DisposalMethod::KEEP)
  , mBlend(BlendMethod::OVER)
  , mTimeout(0)
 {
-  png_uint_16 delay_num, delay_den;
-  // delay, in seconds is delay_num/delay_den
-  png_byte dispose_op;
-  png_byte blend_op;
-  delay_num = png_get_next_frame_delay_num(aPNG, aInfo);
-  delay_den = png_get_next_frame_delay_den(aPNG, aInfo);
-  dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
-  blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
-
-  if (delay_num == 0) {
-    mTimeout = 0; // SetFrameTimeout() will set to a minimum
-  } else {
-    if (delay_den == 0) {
-      delay_den = 100; // so says the APNG spec
-    }
-
-    // Need to cast delay_num to float to have a proper division and
-    // the result to int to avoid compiler warning
-    mTimeout = static_cast<int32_t>(static_cast<double>(delay_num) *
-                                    1000 / delay_den);
-  }
+  png_byte dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
+  png_byte blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
 
   if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) {
     mDispose = DisposalMethod::RESTORE_PREVIOUS;
   } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) {
     mDispose = DisposalMethod::CLEAR;
   } else {
     mDispose = DisposalMethod::KEEP;
   }
 
   if (blend_op == PNG_BLEND_OP_SOURCE) {
     mBlend = BlendMethod::SOURCE;
   } else {
     mBlend = BlendMethod::OVER;
   }
+
+  mTimeout = GetNextFrameDelay(aPNG, aInfo);
 }
 #endif
 
 // First 8 bytes of a PNG file
 const uint8_t
 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
 
 nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
@@ -590,28 +593,35 @@ nsPNGDecoder::info_callback(png_structp 
   if (channels == 1 || channels == 3) {
     decoder->format = gfx::SurfaceFormat::B8G8R8X8;
   } else if (channels == 2 || channels == 4) {
     decoder->format = gfx::SurfaceFormat::B8G8R8A8;
   } else {
     png_longjmp(decoder->mPNG, 1); // invalid number of channels
   }
 
+#ifdef PNG_APNG_SUPPORTED
+  bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL);
+  if (isAnimated) {
+    decoder->PostIsAnimated(GetNextFrameDelay(png_ptr, info_ptr));
+  }
+#endif
+
   if (decoder->IsMetadataDecode()) {
     decoder->CheckForTransparency(decoder->format,
                                   IntRect(0, 0, width, height));
 
-    // We have the size and transparency information we're looking for, so we
-    // don't need to decode any further.
+    // We have the metadata we're looking for, so we don't need to decode any
+    // further.
     decoder->mSuccessfulEarlyFinish = true;
     png_longjmp(decoder->mPNG, 1);
   }
 
 #ifdef PNG_APNG_SUPPORTED
-  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
+  if (isAnimated) {
     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
                                  nullptr);
   }
 
   if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
     decoder->mFrameIsHidden = true;
   } else {
 #endif
--- a/image/moz.build
+++ b/image/moz.build
@@ -54,17 +54,16 @@ UNIFIED_SOURCES += [
     'Decoder.cpp',
     'DecoderFactory.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
     'FrozenImage.cpp',
     'Image.cpp',
     'ImageCacheKey.cpp',
     'ImageFactory.cpp',
-    'ImageMetadata.cpp',
     'ImageOps.cpp',
     'ImageWrapper.cpp',
     'imgFrame.cpp',
     'imgTools.cpp',
     'MultipartImage.cpp',
     'OrientedImage.cpp',
     'ScriptedNotificationObserver.cpp',
     'ShutdownTracker.cpp',
--- a/image/test/gtest/Common.cpp
+++ b/image/test/gtest/Common.cpp
@@ -10,16 +10,17 @@
 
 #include "nsDirectoryServiceDefs.h"
 #include "nsIDirectoryService.h"
 #include "nsIFile.h"
 #include "nsIInputStream.h"
 #include "nsIProperties.h"
 #include "nsNetUtil.h"
 #include "mozilla/nsRefPtr.h"
+#include "nsStreamUtils.h"
 #include "nsString.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 using std::abs;
 
@@ -66,16 +67,25 @@ LoadFile(const char* aRelativePath)
   // working directory.
   file->AppendNative(nsAutoCString(aRelativePath));
 
   // Construct an input stream for the requested file.
   nsCOMPtr<nsIInputStream> inputStream;
   rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file);
   ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
 
+  // Ensure the resulting input stream is buffered.
+  if (!NS_InputStreamIsBuffered(inputStream)) {
+    nsCOMPtr<nsIInputStream> bufStream;
+    rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
+                                   inputStream, 1024);
+    ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
+    inputStream = bufStream;
+  }
+
   return inputStream.forget();
 }
 
 bool
 IsSolidColor(SourceSurface* aSurface, BGRAColor aColor, bool aFuzzy)
 {
   nsRefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
   ASSERT_TRUE_OR_RETURN(dataSurface != nullptr, false);
@@ -139,23 +149,24 @@ ImageTestCase GreenICOTestCase()
   // This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default
   // when the BMP is embedded in an ICO, so it's transparent.
   return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100),
                        TEST_CASE_IS_TRANSPARENT);
 }
 
 ImageTestCase GreenFirstFrameAnimatedGIFTestCase()
 {
-  return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100));
+  return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100),
+                       TEST_CASE_IS_ANIMATED);
 }
 
 ImageTestCase GreenFirstFrameAnimatedPNGTestCase()
 {
   return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100),
-                       TEST_CASE_IS_TRANSPARENT);
+                       TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED);
 }
 
 ImageTestCase CorruptTestCase()
 {
   return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100),
                        TEST_CASE_HAS_ERROR);
 }
 
@@ -193,9 +204,18 @@ ImageTestCase RLE4BMPTestCase()
 }
 
 ImageTestCase RLE8BMPTestCase()
 {
   return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32),
                        TEST_CASE_IS_TRANSPARENT);
 }
 
+ImageTestCase NoFrameDelayGIFTestCase()
+{
+  // This is an invalid (or at least, questionably valid) GIF that's animated
+  // even though it specifies a frame delay of zero. It's animated, but it's not
+  // marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that
+  // it's animated.
+  return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100));
+}
+
 } // namespace mozilla
--- a/image/test/gtest/Common.h
+++ b/image/test/gtest/Common.h
@@ -16,18 +16,19 @@ namespace mozilla {
 ///////////////////////////////////////////////////////////////////////////////
 // Types
 ///////////////////////////////////////////////////////////////////////////////
 
 enum TestCaseFlags
 {
   TEST_CASE_DEFAULT_FLAGS   = 0,
   TEST_CASE_IS_FUZZY        = 1 << 0,
-  TEST_CASE_IS_TRANSPARENT  = 1 << 1,
-  TEST_CASE_HAS_ERROR       = 1 << 2
+  TEST_CASE_HAS_ERROR       = 1 << 1,
+  TEST_CASE_IS_TRANSPARENT  = 1 << 2,
+  TEST_CASE_IS_ANIMATED     = 1 << 3,
 };
 
 struct ImageTestCase
 {
   ImageTestCase(const char* aPath,
                 const char* aMimeType,
                 gfx::IntSize aSize,
                 uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS)
@@ -92,16 +93,17 @@ ImageTestCase GreenICOTestCase();
 ImageTestCase GreenFirstFrameAnimatedGIFTestCase();
 ImageTestCase GreenFirstFrameAnimatedPNGTestCase();
 
 ImageTestCase CorruptTestCase();
 
 ImageTestCase TransparentPNGTestCase();
 ImageTestCase TransparentGIFTestCase();
 ImageTestCase FirstFramePaddingGIFTestCase();
+ImageTestCase NoFrameDelayGIFTestCase();
 
 ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase();
 ImageTestCase RLE4BMPTestCase();
 ImageTestCase RLE8BMPTestCase();
 
 } // namespace mozilla
 
 #endif // mozilla_image_test_gtest_Common_h
--- a/image/test/gtest/TestMetadata.cpp
+++ b/image/test/gtest/TestMetadata.cpp
@@ -5,16 +5,17 @@
 #include "gtest/gtest.h"
 
 #include "Common.h"
 #include "Decoder.h"
 #include "DecoderFactory.h"
 #include "decoders/nsBMPDecoder.h"
 #include "imgIContainer.h"
 #include "imgITools.h"
+#include "ImageFactory.h"
 #include "mozilla/gfx/2D.h"
 #include "nsComponentManagerUtils.h"
 #include "nsCOMPtr.h"
 #include "nsIInputStream.h"
 #include "nsIRunnable.h"
 #include "nsIThread.h"
 #include "mozilla/nsRefPtr.h"
 #include "nsStreamUtils.h"
@@ -32,64 +33,61 @@ TEST(ImageMetadata, ImageModuleAvailable
   // We can run into problems if XPCOM modules get initialized in the wrong
   // order. It's important that this test run first, both as a sanity check and
   // to ensure we get the module initialization order we want.
   nsCOMPtr<imgITools> imgTools =
     do_CreateInstance("@mozilla.org/image/tools;1");
   EXPECT_TRUE(imgTools != nullptr);
 }
 
+enum class BMPAlpha
+{
+  DISABLED,
+  ENABLED
+};
+
 static void
-CheckMetadata(const ImageTestCase& aTestCase, bool aEnableBMPAlpha = false)
+CheckMetadata(const ImageTestCase& aTestCase,
+              BMPAlpha aBMPAlpha = BMPAlpha::DISABLED)
 {
   nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
   ASSERT_TRUE(inputStream != nullptr);
 
-  // Prepare the input stream.
-  nsresult rv;
-  if (!NS_InputStreamIsBuffered(inputStream)) {
-    nsCOMPtr<nsIInputStream> bufStream;
-    rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
-                                   inputStream, 1024);
-    if (NS_SUCCEEDED(rv)) {
-      inputStream = bufStream;
-    }
-  }
-
   // Figure out how much data we have.
   uint64_t length;
-  rv = inputStream->Available(&length);
+  nsresult rv = inputStream->Available(&length);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
 
   // Write the data into a SourceBuffer.
   nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
   sourceBuffer->ExpectLength(length);
   rv = sourceBuffer->AppendFromInputStream(inputStream, length);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
   sourceBuffer->Complete(NS_OK);
 
   // Create a metadata decoder.
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   nsRefPtr<Decoder> decoder =
     DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer);
   ASSERT_TRUE(decoder != nullptr);
 
-  if (aEnableBMPAlpha) {
+  if (aBMPAlpha == BMPAlpha::ENABLED) {
     static_cast<nsBMPDecoder*>(decoder.get())->SetUseAlphaData(true);
   }
 
   // Run the metadata decoder synchronously.
   decoder->Decode();
   
   // Ensure that the metadata decoder didn't make progress it shouldn't have
   // (which would indicate that it decoded past the header of the image).
   Progress metadataProgress = decoder->TakeProgress();
   EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE |
-                                         FLAG_HAS_TRANSPARENCY)));
+                                         FLAG_HAS_TRANSPARENCY |
+                                         FLAG_IS_ANIMATED)));
 
   // If the test case is corrupt, assert what we can and return early.
   if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
     EXPECT_TRUE(decoder->GetDecodeDone());
     EXPECT_TRUE(decoder->HasError());
     return;
   }
 
@@ -97,28 +95,31 @@ CheckMetadata(const ImageTestCase& aTest
 
   // Check that we got the expected metadata.
   EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE);
 
   IntSize metadataSize = decoder->GetSize();
   EXPECT_EQ(aTestCase.mSize.width, metadataSize.width);
   EXPECT_EQ(aTestCase.mSize.height, metadataSize.height);
 
-  bool expectTransparency = aEnableBMPAlpha
+  bool expectTransparency = aBMPAlpha == BMPAlpha::ENABLED
                           ? true
                           : bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT);
   EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY));
 
+  EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
+            bool(metadataProgress & FLAG_IS_ANIMATED));
+
   // Create a full decoder, so we can compare the result.
   decoder =
     DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
                                            imgIContainer::DECODE_FLAGS_DEFAULT);
   ASSERT_TRUE(decoder != nullptr);
 
-  if (aEnableBMPAlpha) {
+  if (aBMPAlpha == BMPAlpha::ENABLED) {
     static_cast<nsBMPDecoder*>(decoder.get())->SetUseAlphaData(true);
   }
 
   // Run the full decoder synchronously.
   decoder->Decode();
   
   EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
   Progress fullProgress = decoder->TakeProgress();
@@ -159,22 +160,95 @@ TEST(ImageMetadata, AnimatedPNG)
 
 TEST(ImageMetadata, FirstFramePaddingGIF)
 {
   CheckMetadata(FirstFramePaddingGIFTestCase());
 }
 
 TEST(ImageMetadata, TransparentBMPWithBMPAlphaOff)
 {
-  CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(),
-                /* aEnableBMPAlpha = */ false);
+  CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), BMPAlpha::ENABLED);
 }
 
 TEST(ImageMetadata, TransparentBMPWithBMPAlphaOn)
 {
-  CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(),
-                /* aEnableBMPAlpha = */ true);
+  CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), BMPAlpha::ENABLED);
 }
 
 TEST(ImageMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); }
 TEST(ImageMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); }
 
 TEST(ImageMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); }
+
+TEST(ImageMetadata, NoFrameDelayGIF)
+{
+  CheckMetadata(NoFrameDelayGIFTestCase());
+}
+
+TEST(ImageMetadata, NoFrameDelayGIFFullDecode)
+{
+  ImageTestCase testCase = NoFrameDelayGIFTestCase();
+
+  // The previous test (NoFrameDelayGIF) verifies that we *don't* detect that
+  // this test case is animated, because it has a zero frame delay for the first
+  // frame. This test verifies that when we do a full decode, we detect the
+  // animation at that point and successfully decode all the frames.
+
+  // Create an image.
+  nsRefPtr<Image> image =
+    ImageFactory::CreateAnonymousImage(nsAutoCString(testCase.mMimeType));
+  ASSERT_TRUE(!image->HasError());
+
+  nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
+  ASSERT_TRUE(inputStream != nullptr);
+
+  // Figure out how much data we have.
+  uint64_t length;
+  nsresult rv = inputStream->Available(&length);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  // Write the data into the image.
+  rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
+                                   static_cast<uint32_t>(length));
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  // Let the image know we've sent all the data.
+  rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  nsRefPtr<ProgressTracker> tracker = image->GetProgressTracker();
+  tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
+
+  // Use GetFrame() to force a sync decode of the image.
+  nsRefPtr<SourceSurface> surface =
+    image->GetFrame(imgIContainer::FRAME_CURRENT,
+                    imgIContainer::FLAG_SYNC_DECODE);
+
+  // Ensure that the image's metadata meets our expectations.
+  IntSize imageSize(0, 0);
+  rv = image->GetWidth(&imageSize.width);
+  EXPECT_TRUE(NS_SUCCEEDED(rv));
+  rv = image->GetHeight(&imageSize.height);
+  EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+  EXPECT_EQ(testCase.mSize.width, imageSize.width);
+  EXPECT_EQ(testCase.mSize.height, imageSize.height);
+
+  Progress imageProgress = tracker->GetProgress();
+
+  EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
+  EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
+
+  // Ensure that we decoded both frames of the image.
+  LookupResult firstFrameLookupResult =
+    SurfaceCache::Lookup(ImageKey(image.get()),
+                         RasterSurfaceKey(imageSize,
+                                          imgIContainer::DECODE_FLAGS_DEFAULT,
+                                          /* aFrameNum = */ 0));
+  EXPECT_EQ(MatchType::EXACT, firstFrameLookupResult.Type());
+                                                             
+  LookupResult secondFrameLookupResult =
+    SurfaceCache::Lookup(ImageKey(image.get()),
+                         RasterSurfaceKey(imageSize,
+                                          imgIContainer::DECODE_FLAGS_DEFAULT,
+                                          /* aFrameNum = */ 1));
+  EXPECT_EQ(MatchType::EXACT, secondFrameLookupResult.Type());
+}
--- a/image/test/gtest/moz.build
+++ b/image/test/gtest/moz.build
@@ -19,16 +19,17 @@ TEST_HARNESS_FILES.gtest += [
     'first-frame-green.gif',
     'first-frame-green.png',
     'first-frame-padding.gif',
     'green.bmp',
     'green.gif',
     'green.ico',
     'green.jpg',
     'green.png',
+    'no-frame-delay.gif',
     'rle4.bmp',
     'rle8.bmp',
     'transparent.bmp',
     'transparent.gif',
     'transparent.png',
 ]
 
 LOCAL_INCLUDES += [
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1c50b67431fb99d6542dbb5433b1a29641dc2c4a
GIT binary patch
literal 317
zc${<hbhEHbOkqf2_`txx@E?d76o0ZXfa(9-ey$<G&W-`DMtTO!j6fkBkT^&&15<HJ
z|H{*E`4`XGa;tmuy*<DA+a7sLd)B$^)v0aoxQ~DGx&E#9`LA=||MBy1wI5mZvBO7e
z`k7^)IeWd9Us?6F>ulEcJL|r4^K~D8vgv1!-|Fjcw*BVqKmGj6uD^ZfUw{9z??1nU
jhJ{B&Mn%Vr6+2Gcc=3ZHGXmi>xH}r^>+2Z?$~D#i?S!7@
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -356,33 +356,16 @@ js::obj_toString(JSContext* cx, unsigned
     /* Steps 4-5. */
     JSString* str = JS_BasicObjectToString(cx, obj);
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
-/* ES5 15.2.4.3. */
-static bool
-obj_toLocaleString(JSContext* cx, unsigned argc, Value* vp)
-{
-    JS_CHECK_RECURSION(cx, return false);
-
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    /* Step 1. */
-    RootedObject obj(cx, ToObject(cx, args.thisv()));
-    if (!obj)
-        return false;
-
-    /* Steps 2-4. */
-    RootedId id(cx, NameToId(cx->names().toString));
-    return obj->callMethod(cx, id, 0, nullptr, args.rval());
-}
 
 bool
 js::obj_valueOf(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
@@ -994,17 +977,17 @@ ProtoSetter(JSContext* cx, unsigned argc
     return true;
 }
 
 static const JSFunctionSpec object_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str,             obj_toSource,                0,0),
 #endif
     JS_FN(js_toString_str,             obj_toString,                0,0),
-    JS_FN(js_toLocaleString_str,       obj_toLocaleString,          0,0),
+    JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0,JSPROP_DEFINE_LATE),
     JS_FN(js_valueOf_str,              obj_valueOf,                 0,0),
 #if JS_HAS_OBJ_WATCHPOINT
     JS_FN(js_watch_str,                obj_watch,                   2,0),
     JS_FN(js_unwatch_str,              obj_unwatch,                 1,0),
 #endif
     JS_FN(js_hasOwnProperty_str,       obj_hasOwnProperty,          1,0),
     JS_FN(js_isPrototypeOf_str,        obj_isPrototypeOf,           1,0),
     JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable,    1,0),
--- a/js/src/builtin/Object.js
+++ b/js/src/builtin/Object.js
@@ -46,16 +46,25 @@ function ObjectGetPrototypeOf(obj) {
     return std_Reflect_getPrototypeOf(ToObject(obj));
 }
 
 /* ES6 draft rev 32 (2015 Feb 2) 19.1.2.11. */
 function ObjectIsExtensible(obj) {
     return IsObject(obj) && std_Reflect_isExtensible(obj);
 }
 
+/* ES2015 19.1.3.5 Object.prototype.toLocaleString */
+function Object_toLocaleString() {
+    // Step 1.
+    var O = this;
+
+    // Step 2.
+    return O.toString();
+}
+
 function ObjectDefineSetter(name, setter) {
     var object;
     if (this === null || this === undefined)
         object = global;
     else
         object = ToObject(this);
 
     if (!IsCallable(setter))
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1917,17 +1917,17 @@ TypedObject::obj_setProperty(JSContext* 
                 return false;
             }
             return result.failReadOnly();
         }
 
         uint32_t index;
         if (IdIsIndex(id, &index)) {
             if (!receiver.isObject() || obj != &receiver.toObject())
-                return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
+                return SetPropertyByDefining(cx, obj, id, v, receiver, result);
 
             if (index >= uint32_t(typedObj->length())) {
                 JS_ReportErrorNumber(cx, GetErrorMessage,
                                      nullptr, JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX);
                 return false;
             }
 
             Rooted<TypeDescr*> elementType(cx);
@@ -1943,17 +1943,17 @@ TypedObject::obj_setProperty(JSContext* 
       case type::Struct: {
         Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());
 
         size_t fieldIndex;
         if (!descr->fieldIndex(id, &fieldIndex))
             break;
 
         if (!receiver.isObject() || obj != &receiver.toObject())
-            return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
+            return SetPropertyByDefining(cx, obj, id, v, receiver, result);
 
         size_t offset = descr->fieldOffset(fieldIndex);
         Rooted<TypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
         RootedAtom fieldName(cx, &descr->fieldName(fieldIndex));
         if (!ConvertAndCopyTo(cx, fieldType, typedObj, offset, fieldName, v))
             return false;
         return result.succeed();
       }
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -458,175 +458,127 @@ FoldType(ExclusiveContext* cx, ParseNode
             break;
 
           default:;
         }
     }
     return true;
 }
 
-/*
- * Fold two numeric constants.  Beware that pn1 and pn2 are recycled, unless
- * one of them aliases pn, so you can't safely fetch pn2->pn_next, e.g., after
- * a successful call to this function.
- */
-static bool
-FoldBinaryNumeric(ExclusiveContext* cx, JSOp op, ParseNode* pn1, ParseNode* pn2,
-                  ParseNode* pn)
-{
-    double d, d2;
-    int32_t i, j;
-
-    MOZ_ASSERT(pn1->isKind(PNK_NUMBER) && pn2->isKind(PNK_NUMBER));
-    d = pn1->pn_dval;
-    d2 = pn2->pn_dval;
-    switch (op) {
-      case JSOP_LSH:
-      case JSOP_RSH:
-        i = ToInt32(d);
-        j = ToInt32(d2);
-        j &= 31;
-        d = int32_t((op == JSOP_LSH) ? uint32_t(i) << j : i >> j);
-        break;
-
-      case JSOP_URSH:
-        j = ToInt32(d2);
-        j &= 31;
-        d = ToUint32(d) >> j;
-        break;
-
-      case JSOP_ADD:
-        d += d2;
-        break;
-
-      case JSOP_SUB:
-        d -= d2;
-        break;
-
-      case JSOP_MUL:
-        d *= d2;
-        break;
-
-      case JSOP_DIV:
-        if (d2 == 0) {
-#if defined(XP_WIN)
-            /* XXX MSVC miscompiles such that (NaN == 0) */
-            if (IsNaN(d2))
-                d = GenericNaN();
-            else
-#endif
-            if (d == 0 || IsNaN(d))
-                d = GenericNaN();
-            else if (IsNegative(d) != IsNegative(d2))
-                d = NegativeInfinity<double>();
-            else
-                d = PositiveInfinity<double>();
-        } else {
-            d /= d2;
-        }
-        break;
-
-      case JSOP_MOD:
-        if (d2 == 0) {
-            d = GenericNaN();
-        } else {
-            d = js_fmod(d, d2);
-        }
-        break;
-
-      case JSOP_POW:
-        d = ecmaPow(d, d2);
-        break;
-
-      default:;
-    }
-
-    /* Take care to allow pn1 or pn2 to alias pn. */
-    pn->setKind(PNK_NUMBER);
-    pn->setOp(JSOP_DOUBLE);
-    pn->setArity(PN_NULLARY);
-    pn->pn_dval = d;
-    return true;
-}
-
 // Remove a ParseNode, **pnp, from a parse tree, putting another ParseNode,
 // *pn, in its place.
 //
 // pnp points to a ParseNode pointer. This must be the only pointer that points
 // to the parse node being replaced. The replacement, *pn, is unchanged except
 // for its pn_next pointer; updating that is necessary if *pn's new parent is a
 // list node.
 static void
 ReplaceNode(ParseNode** pnp, ParseNode* pn)
 {
     pn->pn_next = (*pnp)->pn_next;
     *pnp = pn;
 }
 
+static bool
+IsEffectless(ParseNode* node)
+{
+    return node->isKind(PNK_TRUE) ||
+           node->isKind(PNK_FALSE) ||
+           node->isKind(PNK_STRING) ||
+           node->isKind(PNK_TEMPLATE_STRING) ||
+           node->isKind(PNK_NUMBER) ||
+           node->isKind(PNK_NULL) ||
+           node->isKind(PNK_FUNCTION) ||
+           node->isKind(PNK_GENEXP);
+}
+
 enum Truthiness { Truthy, Falsy, Unknown };
 
 static Truthiness
 Boolish(ParseNode* pn)
 {
     switch (pn->getKind()) {
       case PNK_NUMBER:
         return (pn->pn_dval != 0 && !IsNaN(pn->pn_dval)) ? Truthy : Falsy;
 
       case PNK_STRING:
+      case PNK_TEMPLATE_STRING:
         return (pn->pn_atom->length() > 0) ? Truthy : Falsy;
 
       case PNK_TRUE:
       case PNK_FUNCTION:
       case PNK_GENEXP:
         return Truthy;
 
       case PNK_FALSE:
       case PNK_NULL:
         return Falsy;
 
+      case PNK_VOID: {
+        // |void <foo>| evaluates to |undefined| which isn't truthy.  But the
+        // sense of this method requires that the expression be literally
+        // replaceable with true/false: not the case if the nested expression
+        // is effectful, might throw, &c.  Walk past the |void| (and nested
+        // |void| expressions, for good measure) and check that the nested
+        // expression doesn't break this requirement before indicating falsity.
+        do {
+            pn = pn->pn_kid;
+        } while (pn->isKind(PNK_VOID));
+
+        return IsEffectless(pn) ? Falsy : Unknown;
+      }
+
       default:
         return Unknown;
     }
 }
 
-// Expressions that appear in a few specific places are treated specially
-// during constant folding. This enum tells where a parse node appears.
-enum class SyntacticContext : int {
-    // pn is an expression, and it appears in a context where only its side
-    // effects and truthiness matter: the condition of an if statement,
-    // conditional expression, while loop, or for(;;) loop; or an operand of &&
-    // or || in such a context.
-    Condition,
-
-    // pn is the operand of the 'delete' keyword.
-    Delete,
-
-    // Any other syntactic context.
-    Other
-};
-
-static SyntacticContext
-condIf(const ParseNode* pn, ParseNodeKind kind)
-{
-    return pn->isKind(kind) ? SyntacticContext::Condition : SyntacticContext::Other;
-}
+static bool
+Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda);
 
 static bool
-Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda,
-     SyntacticContext sc);
+FoldCondition(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+              bool inGenexpLambda)
+{
+    // Conditions fold like any other expression...
+    if (!Fold(cx, nodePtr, parser, inGenexpLambda))
+        return false;
+
+    // ...but then they sometimes can be further folded to constants.
+    ParseNode* node = *nodePtr;
+    Truthiness t = Boolish(node);
+    if (t != Unknown) {
+        // We can turn function nodes into constant nodes here, but mutating
+        // function nodes is tricky --- in particular, mutating a function node
+        // that appears on a method list corrupts the method list. However,
+        // methods are M's in statements of the form 'this.foo = M;', which we
+        // never fold, so we're okay.
+        parser.prepareNodeForMutation(node);
+        if (t == Truthy) {
+            node->setKind(PNK_TRUE);
+            node->setOp(JSOP_TRUE);
+        } else {
+            node->setKind(PNK_FALSE);
+            node->setOp(JSOP_FALSE);
+        }
+        node->setArity(PN_NULLARY);
+    }
+
+    return true;
+}
 
 static bool
 FoldTypeOfExpr(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
                bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_TYPEOFEXPR));
     MOZ_ASSERT(node->isArity(PN_UNARY));
 
     ParseNode*& expr = node->pn_kid;
-    if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other))
+    if (!Fold(cx, &expr, parser, inGenexpLambda))
         return false;
 
     // Constant-fold the entire |typeof| if given a constant with known type.
     RootedPropertyName result(cx);
     if (expr->isKind(PNK_STRING) || expr->isKind(PNK_TEMPLATE_STRING))
         result = cx->names().string;
     else if (expr->isKind(PNK_NUMBER))
         result = cx->names().number;
@@ -645,66 +597,29 @@ FoldTypeOfExpr(ExclusiveContext* cx, Par
         node->setOp(JSOP_NOP);
         node->pn_atom = result;
     }
 
     return true;
 }
 
 static bool
-FoldVoid(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
-         bool inGenexpLambda, SyntacticContext sc)
-{
-    MOZ_ASSERT(node->isKind(PNK_VOID));
-    MOZ_ASSERT(node->isArity(PN_UNARY));
-
-    ParseNode*& expr = node->pn_kid;
-    if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other))
-        return false;
-
-    if (sc == SyntacticContext::Condition) {
-        if (expr->isKind(PNK_TRUE) ||
-            expr->isKind(PNK_FALSE) ||
-            expr->isKind(PNK_STRING) ||
-            expr->isKind(PNK_TEMPLATE_STRING) ||
-            expr->isKind(PNK_NUMBER) ||
-            expr->isKind(PNK_NULL) ||
-            expr->isKind(PNK_FUNCTION))
-        {
-            parser.prepareNodeForMutation(node);
-            node->setKind(PNK_FALSE);
-            node->setArity(PN_NULLARY);
-            node->setOp(JSOP_FALSE);
-        }
-    }
-
-    return true;
-}
-
-static bool
 FoldDeleteExpr(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
                bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_DELETEEXPR));
     MOZ_ASSERT(node->isArity(PN_UNARY));
 
     ParseNode*& expr = node->pn_kid;
-    if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other))
+    if (!Fold(cx, &expr, parser, inGenexpLambda))
         return false;
 
-    // Expression deletion evaluates the expression, then evaluates to
-    // true.  For trivial expressions, eliminate the expression evaluation.
-    if (expr->isKind(PNK_TRUE) ||
-        expr->isKind(PNK_FALSE) ||
-        expr->isKind(PNK_STRING) ||
-        expr->isKind(PNK_TEMPLATE_STRING) ||
-        expr->isKind(PNK_NUMBER) ||
-        expr->isKind(PNK_NULL) ||
-        expr->isKind(PNK_FUNCTION))
-    {
+    // Expression deletion evaluates the expression, then evaluates to true.
+    // For effectless expressions, eliminate the expression evaluation.
+    if (IsEffectless(expr)) {
         parser.prepareNodeForMutation(node);
         node->setKind(PNK_TRUE);
         node->setArity(PN_NULLARY);
         node->setOp(JSOP_TRUE);
     }
 
     return true;
 }
@@ -713,17 +628,17 @@ static bool
 FoldDeleteElement(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
                   bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_DELETEELEM) || node->isKind(PNK_DELETESUPERELEM));
     MOZ_ASSERT(node->isArity(PN_UNARY));
     MOZ_ASSERT(node->pn_kid->isKind(PNK_ELEM) || node->pn_kid->isKind(PNK_SUPERELEM));
 
     ParseNode*& expr = node->pn_kid;
-    if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other))
+    if (!Fold(cx, &expr, parser, inGenexpLambda))
         return false;
 
     // If we're deleting an element, but constant-folding converted our
     // element reference into a dotted property access, we must *also*
     // morph the node's kind.
     //
     // In principle this also applies to |super["foo"] -> super.foo|,
     // but we don't constant-fold |super["foo"]| yet.
@@ -744,34 +659,34 @@ FoldDeleteProperty(ExclusiveContext* cx,
     MOZ_ASSERT(node->isArity(PN_UNARY));
     MOZ_ASSERT(node->pn_kid->isKind(PNK_DOT) || node->pn_kid->isKind(PNK_SUPERPROP));
 
     ParseNode*& expr = node->pn_kid;
 #ifdef DEBUG
     ParseNodeKind oldKind = expr->getKind();
 #endif
 
-    if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other))
+    if (!Fold(cx, &expr, parser, inGenexpLambda))
         return false;
 
     MOZ_ASSERT(expr->isKind(oldKind),
                "kind should have remained invariant under folding");
 
     return true;
 }
 
 static bool
 FoldNot(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
         bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_NOT));
     MOZ_ASSERT(node->isArity(PN_UNARY));
 
     ParseNode*& expr = node->pn_kid;
-    if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Condition))
+    if (!FoldCondition(cx, &expr, parser, inGenexpLambda))
         return false;
 
     if (expr->isKind(PNK_NUMBER)) {
         double d = expr->pn_dval;
 
         parser.prepareNodeForMutation(node);
         if (d == 0 || IsNaN(d)) {
             node->setKind(PNK_TRUE);
@@ -797,17 +712,17 @@ static bool
 FoldUnaryArithmetic(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
                     bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_BITNOT) || node->isKind(PNK_POS) || node->isKind(PNK_NEG),
                "need a different method for this node kind");
     MOZ_ASSERT(node->isArity(PN_UNARY));
 
     ParseNode*& expr = node->pn_kid;
-    if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other))
+    if (!Fold(cx, &expr, parser, inGenexpLambda))
         return false;
 
     if (expr->isKind(PNK_NUMBER) || expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE)) {
         double d = expr->isKind(PNK_NUMBER)
                    ? expr->pn_dval
                    : double(expr->isKind(PNK_TRUE));
 
         if (node->isKind(PNK_BITNOT))
@@ -835,38 +750,37 @@ FoldIncrementDecrement(ExclusiveContext*
                node->isKind(PNK_POSTINCREMENT) ||
                node->isKind(PNK_PREDECREMENT) ||
                node->isKind(PNK_POSTDECREMENT));
     MOZ_ASSERT(node->isArity(PN_UNARY));
 
     ParseNode*& target = node->pn_kid;
     MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser<FullParseHandler>::PermitAssignmentToFunctionCalls));
 
-    if (!Fold(cx, &target, parser, inGenexpLambda, SyntacticContext::Other))
+    if (!Fold(cx, &target, parser, inGenexpLambda))
         return false;
 
     MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser<FullParseHandler>::PermitAssignmentToFunctionCalls));
 
     return true;
 }
 
 static bool
 FoldAndOr(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
-          bool inGenexpLambda, SyntacticContext sc)
+          bool inGenexpLambda)
 {
     ParseNode* node = *nodePtr;
 
     MOZ_ASSERT(node->isKind(PNK_AND) || node->isKind(PNK_OR));
     MOZ_ASSERT(node->isArity(PN_LIST));
 
     bool isOrNode = node->isKind(PNK_OR);
     ParseNode** elem = &node->pn_head;
     do {
-        // Pass |sc| through to propagate conditionality.
-        if (!Fold(cx, elem, parser, inGenexpLambda, sc))
+        if (!Fold(cx, elem, parser, inGenexpLambda))
             return false;
 
         Truthiness t = Boolish(*elem);
 
         // If we don't know the constant-folded node's truthiness, we can't
         // reduce this node with its surroundings.  Continue folding any
         // remaining nodes.
         if (t == Unknown) {
@@ -945,36 +859,36 @@ FoldConditional(ExclusiveContext* cx, Pa
         nodePtr = nextNode;
         nextNode = nullptr;
 
         ParseNode* node = *nodePtr;
         MOZ_ASSERT(node->isKind(PNK_CONDITIONAL));
         MOZ_ASSERT(node->isArity(PN_TERNARY));
 
         ParseNode*& expr = node->pn_kid1;
-        if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Condition))
+        if (!FoldCondition(cx, &expr, parser, inGenexpLambda))
             return false;
 
         ParseNode*& ifTruthy = node->pn_kid2;
-        if (!Fold(cx, &ifTruthy, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, &ifTruthy, parser, inGenexpLambda))
             return false;
 
         ParseNode*& ifFalsy = node->pn_kid3;
 
         // If our C?T:F node has F as another ?: node, *iteratively* constant-
         // fold F *after* folding C and T (and possibly eliminating C and one
         // of T/F entirely); otherwise fold F normally.  Making |nextNode| non-
         // null causes this loop to run again to fold F.
         //
         // Conceivably we could instead/also iteratively constant-fold T, if T
         // were more complex than F.  Such an optimization is unimplemented.
         if (ifFalsy->isKind(PNK_CONDITIONAL)) {
             nextNode = &ifFalsy;
         } else {
-            if (!Fold(cx, &ifFalsy, parser, inGenexpLambda, SyntacticContext::Other))
+            if (!Fold(cx, &ifFalsy, parser, inGenexpLambda))
                 return false;
         }
 
         // Try to constant-fold based on the condition expression.
         Truthiness t = Boolish(expr);
         if (t == Unknown)
             continue;
 
@@ -1036,38 +950,35 @@ FoldIf(ExclusiveContext* cx, ParseNode**
         nodePtr = nextNode;
         nextNode = nullptr;
 
         ParseNode* node = *nodePtr;
         MOZ_ASSERT(node->isKind(PNK_IF));
         MOZ_ASSERT(node->isArity(PN_TERNARY));
 
         ParseNode*& expr = node->pn_kid1;
-        if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Condition))
+        if (!FoldCondition(cx, &expr, parser, inGenexpLambda))
             return false;
 
         ParseNode*& consequent = node->pn_kid2;
-        if (!Fold(cx, &consequent, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, &consequent, parser, inGenexpLambda))
             return false;
 
         ParseNode*& alternative = node->pn_kid3;
         if (alternative) {
             // If in |if (C) T; else F;| we have |F| as another |if|,
             // *iteratively* constant-fold |F| *after* folding |C| and |T| (and
             // possibly completely replacing the whole thing with |T| or |F|);
             // otherwise fold F normally.  Making |nextNode| non-null causes
             // this loop to run again to fold F.
             if (alternative->isKind(PNK_IF)) {
                 nextNode = &alternative;
             } else {
-                if (!Fold(cx, &alternative, parser, inGenexpLambda,
-                          SyntacticContext::Other))
-                {
+                if (!Fold(cx, &alternative, parser, inGenexpLambda))
                     return false;
-                }
             }
         }
 
         // Eliminate the consequent or alternative if the condition has
         // constant truthiness.  Don't eliminate if we have an |if (0)| in
         // trailing position in a generator expression, as this is a special
         // form we can't fold away.
         Truthiness t = Boolish(expr);
@@ -1149,26 +1060,65 @@ FoldFunction(ExclusiveContext* cx, Parse
 
     // Don't constant-fold inside "use asm" code, as this could create a parse
     // tree that doesn't type-check as asm.js.
     if (node->pn_funbox->useAsmOrInsideUseAsm())
         return true;
 
     // Note: pn_body is null for lazily-parsed functions.
     if (ParseNode*& functionBody = node->pn_body) {
-        if (!Fold(cx, &functionBody, parser, node->pn_funbox->inGenexpLambda,
-                  SyntacticContext::Other))
-        {
+        if (!Fold(cx, &functionBody, parser, node->pn_funbox->inGenexpLambda))
             return false;
-        }
     }
 
     return true;
 }
 
+static double
+ComputeBinary(ParseNodeKind kind, double left, double right)
+{
+    if (kind == PNK_ADD)
+        return left + right;
+
+    if (kind == PNK_SUB)
+        return left - right;
+
+    if (kind == PNK_STAR)
+        return left * right;
+
+    if (kind == PNK_MOD)
+        return right == 0 ? GenericNaN() : js_fmod(left, right);
+
+    if (kind == PNK_URSH)
+        return ToUint32(left) >> (ToUint32(right) & 31);
+
+    if (kind == PNK_DIV) {
+        if (right == 0) {
+#if defined(XP_WIN)
+            /* XXX MSVC miscompiles such that (NaN == 0) */
+            if (IsNaN(right))
+                return GenericNaN();
+#endif
+            if (left == 0 || IsNaN(left))
+                return GenericNaN();
+            if (IsNegative(left) != IsNegative(right))
+                return NegativeInfinity<double>();
+            return PositiveInfinity<double>();
+        }
+
+        return left / right;
+    }
+
+    MOZ_ASSERT(kind == PNK_LSH || kind == PNK_RSH);
+
+    int32_t i = ToInt32(left);
+    uint32_t j = ToUint32(right) & 31;
+    return int32_t((kind == PNK_LSH) ? uint32_t(i) << j : i >> j);
+}
+
 static bool
 FoldBinaryArithmetic(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
                      bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_SUB) ||
                node->isKind(PNK_STAR) ||
                node->isKind(PNK_LSH) ||
                node->isKind(PNK_RSH) ||
@@ -1176,47 +1126,51 @@ FoldBinaryArithmetic(ExclusiveContext* c
                node->isKind(PNK_DIV) ||
                node->isKind(PNK_MOD));
     MOZ_ASSERT(node->isArity(PN_LIST));
     MOZ_ASSERT(node->pn_count >= 2);
 
     // Fold each operand, ideally into a number.
     ParseNode** listp = &node->pn_head;
     for (; *listp; listp = &(*listp)->pn_next) {
-        if (!Fold(cx, listp, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, listp, parser, inGenexpLambda))
             return false;
 
         if (!FoldType(cx, *listp, PNK_NUMBER))
             return false;
     }
 
     // Repoint the list's tail pointer.
     node->pn_tail = listp;
 
     // Now fold all leading numeric terms together into a single number.
     // (Trailing terms for the non-shift operations can't be folded together
     // due to floating point imprecision.  For example, if |x === -2**53|,
     // |x - 1 - 1 === -2**53| but |x - 2 === -2**53 - 2|.  Shifts could be
     // folded, but it doesn't seem worth the effort.)
-    JSOp op = node->getOp();
     ParseNode* elem = node->pn_head;
     ParseNode* next = elem->pn_next;
     if (elem->isKind(PNK_NUMBER)) {
+        ParseNodeKind kind = node->getKind();
         while (true) {
             if (!next || !next->isKind(PNK_NUMBER))
                 break;
 
+            double d = ComputeBinary(kind, elem->pn_dval, next->pn_dval);
+
             ParseNode* afterNext = next->pn_next;
-            if (!FoldBinaryNumeric(cx, op, elem, next, elem))
-                return false;
-
             parser.freeTree(next);
             next = afterNext;
             elem->pn_next = next;
 
+            elem->setKind(PNK_NUMBER);
+            elem->setOp(JSOP_DOUBLE);
+            elem->setArity(PN_NULLARY);
+            elem->pn_dval = d;
+
             node->pn_count--;
         }
 
         if (node->pn_count == 1) {
             MOZ_ASSERT(node->pn_head == elem);
             MOZ_ASSERT(elem->isKind(PNK_NUMBER));
 
             double d = elem->pn_dval;
@@ -1238,17 +1192,17 @@ FoldExponentiation(ExclusiveContext* cx,
 {
     MOZ_ASSERT(node->isKind(PNK_POW));
     MOZ_ASSERT(node->isArity(PN_LIST));
     MOZ_ASSERT(node->pn_count >= 2);
 
     // Fold each operand, ideally into a number.
     ParseNode** listp = &node->pn_head;
     for (; *listp; listp = &(*listp)->pn_next) {
-        if (!Fold(cx, listp, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, listp, parser, inGenexpLambda))
             return false;
 
         if (!FoldType(cx, *listp, PNK_NUMBER))
             return false;
     }
 
     // Repoint the list's tail pointer.
     node->pn_tail = listp;
@@ -1279,17 +1233,17 @@ FoldExponentiation(ExclusiveContext* cx,
 static bool
 FoldList(ExclusiveContext* cx, ParseNode* list, Parser<FullParseHandler>& parser,
          bool inGenexpLambda)
 {
     MOZ_ASSERT(list->isArity(PN_LIST));
 
     ParseNode** elem = &list->pn_head;
     for (; *elem; elem = &(*elem)->pn_next) {
-        if (!Fold(cx, elem, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, elem, parser, inGenexpLambda))
             return false;
     }
 
     // Repoint the list's tail pointer if the final element was replaced.
     list->pn_tail = elem;
 
     list->checkListConsistency();
 
@@ -1299,17 +1253,17 @@ FoldList(ExclusiveContext* cx, ParseNode
 static bool
 FoldReturn(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
            bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_RETURN));
     MOZ_ASSERT(node->isArity(PN_BINARY));
 
     if (ParseNode*& expr = node->pn_left) {
-        if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, &expr, parser, inGenexpLambda))
             return false;
     }
 
 #ifdef DEBUG
     if (ParseNode* generatorSpecific = node->pn_right) {
         MOZ_ASSERT(generatorSpecific->isKind(PNK_NAME));
         MOZ_ASSERT(generatorSpecific->pn_atom->equals(".genrval"));
         MOZ_ASSERT(generatorSpecific->isAssigned());
@@ -1322,86 +1276,427 @@ FoldReturn(ExclusiveContext* cx, ParseNo
 static bool
 FoldTry(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
         bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_TRY));
     MOZ_ASSERT(node->isArity(PN_TERNARY));
 
     ParseNode*& statements = node->pn_kid1;
-    if (!Fold(cx, &statements, parser, inGenexpLambda, SyntacticContext::Other))
+    if (!Fold(cx, &statements, parser, inGenexpLambda))
         return false;
 
     if (ParseNode*& catchList = node->pn_kid2) {
-        if (!Fold(cx, &catchList, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, &catchList, parser, inGenexpLambda))
             return false;
     }
 
     if (ParseNode*& finally = node->pn_kid3) {
-        if (!Fold(cx, &finally, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, &finally, parser, inGenexpLambda))
             return false;
     }
 
     return true;
 }
 
 static bool
 FoldCatch(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
           bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_CATCH));
     MOZ_ASSERT(node->isArity(PN_TERNARY));
 
     ParseNode*& declPattern = node->pn_kid1;
-    if (!Fold(cx, &declPattern, parser, inGenexpLambda, SyntacticContext::Other))
+    if (!Fold(cx, &declPattern, parser, inGenexpLambda))
         return false;
 
     if (ParseNode*& cond = node->pn_kid2) {
-        if (!Fold(cx, &cond, parser, inGenexpLambda, SyntacticContext::Condition))
+        if (!FoldCondition(cx, &cond, parser, inGenexpLambda))
             return false;
     }
 
     if (ParseNode*& statements = node->pn_kid3) {
-        if (!Fold(cx, &statements, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, &statements, parser, inGenexpLambda))
             return false;
     }
 
     return true;
 }
 
 static bool
 FoldClass(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
           bool inGenexpLambda)
 {
     MOZ_ASSERT(node->isKind(PNK_CLASS));
     MOZ_ASSERT(node->isArity(PN_TERNARY));
 
     if (ParseNode*& classNames = node->pn_kid1) {
-        if (!Fold(cx, &classNames, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, &classNames, parser, inGenexpLambda))
             return false;
     }
 
     if (ParseNode*& heritage = node->pn_kid2) {
-        if (!Fold(cx, &heritage, parser, inGenexpLambda, SyntacticContext::Other))
+        if (!Fold(cx, &heritage, parser, inGenexpLambda))
             return false;
     }
 
     ParseNode*& body = node->pn_kid3;
-    return Fold(cx, &body, parser, inGenexpLambda, SyntacticContext::Other);
+    return Fold(cx, &body, parser, inGenexpLambda);
+}
+
+static bool
+FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+            bool inGenexpLambda)
+{
+    ParseNode* node = *nodePtr;
+
+    MOZ_ASSERT(node->isKind(PNK_ELEM));
+    MOZ_ASSERT(node->isArity(PN_BINARY));
+
+    ParseNode*& expr = node->pn_left;
+    if (!Fold(cx, &expr, parser, inGenexpLambda))
+        return false;
+
+    ParseNode*& key = node->pn_right;
+    if (!Fold(cx, &key, parser, inGenexpLambda))
+        return false;
+
+    PropertyName* name = nullptr;
+    if (key->isKind(PNK_STRING)) {
+        JSAtom* atom = key->pn_atom;
+        uint32_t index;
+
+        if (atom->isIndex(&index)) {
+            // Optimization 1: We have something like expr["100"]. This is
+            // equivalent to expr[100] which is faster.
+            key->setKind(PNK_NUMBER);
+            key->setOp(JSOP_DOUBLE);
+            key->pn_dval = index;
+        } else {
+            name = atom->asPropertyName();
+        }
+    } else if (key->isKind(PNK_NUMBER)) {
+        double number = key->pn_dval;
+        if (number != ToUint32(number)) {
+            // Optimization 2: We have something like expr[3.14]. The number
+            // isn't an array index, so it converts to a string ("3.14"),
+            // enabling optimization 3 below.
+            JSAtom* atom = ToAtom<NoGC>(cx, DoubleValue(number));
+            if (!atom)
+                return false;
+            name = atom->asPropertyName();
+        }
+    }
+
+    // If we don't have a name, we can't optimize to getprop.
+    if (!name)
+        return true;
+
+    // Also don't optimize if the name doesn't map directly to its id for TI's
+    // purposes.
+    if (NameToId(name) != IdToTypeId(NameToId(name)))
+        return true;
+
+    // Optimization 3: We have expr["foo"] where foo is not an index.  Convert
+    // to a property access (like expr.foo) that optimizes better downstream.
+    // Don't bother with this for names that TI considers to be indexes, to
+    // simplify downstream analysis.
+    ParseNode* dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end);
+    if (!dottedAccess)
+        return false;
+    dottedAccess->setInParens(node->isInParens());
+    ReplaceNode(nodePtr, dottedAccess);
+
+    // If we've replaced |expr["prop"]| with |expr.prop|, we can now free the
+    // |"prop"| and |expr["prop"]| nodes -- but not the |expr| node that we're
+    // now using as a sub-node of |dottedAccess|.  Munge |expr["prop"]| into a
+    // node with |"prop"| as its only child, that'll pass AST sanity-checking
+    // assertions during freeing, then free it.
+    node->setKind(PNK_TYPEOFEXPR);
+    node->setArity(PN_UNARY);
+    node->pn_kid = key;
+    parser.freeTree(node);
+
+    return true;
+}
+
+static bool
+FoldAdd(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+        bool inGenexpLambda)
+{
+    ParseNode* node = *nodePtr;
+
+    MOZ_ASSERT(node->isKind(PNK_ADD));
+    MOZ_ASSERT(node->isArity(PN_LIST));
+    MOZ_ASSERT(node->pn_count >= 2);
+
+    // Generically fold all operands first.
+    if (!FoldList(cx, node, parser, inGenexpLambda))
+        return false;
+
+    // Fold leading numeric operands together:
+    //
+    //   (1 + 2 + x)  becomes  (3 + x)
+    //
+    // Don't go past the leading operands: additions after a string are
+    // string concatenations, not additions: ("1" + 2 + 3 === "123").
+    ParseNode* current = node->pn_head;
+    ParseNode* next = current->pn_next;
+    if (current->isKind(PNK_NUMBER)) {
+        do {
+            if (!next->isKind(PNK_NUMBER))
+                break;
+
+            current->pn_dval += next->pn_dval;
+            current->pn_next = next->pn_next;
+            parser.freeTree(next);
+            next = current->pn_next;
+
+            MOZ_ASSERT(node->pn_count > 1);
+            node->pn_count--;
+        } while (next);
+    }
+
+    // If any operands remain, attempt string concatenation folding.
+    do {
+        // If no operands remain, we're done.
+        if (!next)
+            break;
+
+        // (number + string) is string concatenation *only* at the start of
+        // the list: (x + 1 + "2" !== x + "12") when x is a number.
+        if (current->isKind(PNK_NUMBER) && next->isKind(PNK_STRING)) {
+            if (!FoldType(cx, current, PNK_STRING))
+                return false;
+            next = current->pn_next;
+        }
+
+        // The first string forces all subsequent additions to be
+        // string concatenations.
+        do {
+            if (current->isKind(PNK_STRING))
+                break;
+
+            current = next;
+            next = next->pn_next;
+        } while (next);
+
+        // If there's nothing left to fold, we're done.
+        if (!next)
+            break;
+
+        RootedString combination(cx);
+        RootedString tmp(cx);
+        do {
+            // Create a rope of the current string and all succeeding
+            // constants that we can convert to strings, then atomize it
+            // and replace them all with that fresh string.
+            MOZ_ASSERT(current->isKind(PNK_STRING));
+
+            combination = current->pn_atom;
+
+            do {
+                // Try folding the next operand to a string.
+                if (!FoldType(cx, next, PNK_STRING))
+                    return false;
+
+                // Stop glomming once folding doesn't produce a string.
+                if (!next->isKind(PNK_STRING))
+                    break;
+
+                // Add this string to the combination and remove the node.
+                tmp = next->pn_atom;
+                combination = ConcatStrings<CanGC>(cx, combination, tmp);
+                if (!combination)
+                    return false;
+
+                current->pn_next = next->pn_next;
+                parser.freeTree(next);
+                next = current->pn_next;
+
+                MOZ_ASSERT(node->pn_count > 1);
+                node->pn_count--;
+            } while (next);
+
+            // Replace |current|'s string with the entire combination.
+            MOZ_ASSERT(current->isKind(PNK_STRING));
+            combination = AtomizeString(cx, combination);
+            if (!combination)
+                return false;
+            current->pn_atom = &combination->asAtom();
+
+
+            // If we're out of nodes, we're done.
+            if (!next)
+                break;
+
+            current = next;
+            next = current->pn_next;
+
+            // If we're out of nodes *after* the non-foldable-to-string
+            // node, we're done.
+            if (!next)
+                break;
+
+            // Otherwise find the next node foldable to a string, and loop.
+            do {
+                current = next;
+                next = current->pn_next;
+
+                if (!FoldType(cx, current, PNK_STRING))
+                    return false;
+                next = current->pn_next;
+            } while (!current->isKind(PNK_STRING) && next);
+        } while (next);
+    } while (false);
+
+    MOZ_ASSERT(!next, "must have considered all nodes here");
+    MOZ_ASSERT(!current->pn_next, "current node must be the last node");
+
+    node->pn_tail = &current->pn_next;
+    node->checkListConsistency();
+
+    if (node->pn_count == 1) {
+        // We reduced the list to a constant.  Replace the PNK_ADD node
+        // with that constant.
+        ReplaceNode(nodePtr, current);
+
+        // Free the old node to aggressively verify nothing uses it.
+        node->setKind(PNK_TRUE);
+        node->setArity(PN_NULLARY);
+        node->setOp(JSOP_TRUE);
+        parser.freeTree(node);
+    }
+
+    return true;
+}
+
+static bool
+FoldCall(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+         bool inGenexpLambda)
+{
+    MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_TAGGED_TEMPLATE));
+    MOZ_ASSERT(node->isArity(PN_LIST));
+
+    // Don't fold a parenthesized callable component in an invocation, as this
+    // might cause a different |this| value to be used, changing semantics:
+    //
+    //   var prop = "global";
+    //   var obj = { prop: "obj", f: function() { return this.prop; } };
+    //   assertEq((true ? obj.f : null)(), "global");
+    //   assertEq(obj.f(), "obj");
+    //   assertEq((true ? obj.f : null)``, "global");
+    //   assertEq(obj.f``, "obj");
+    //
+    // See bug 537673 and bug 1182373.
+    ParseNode** listp = &node->pn_head;
+    if ((*listp)->isInParens())
+        listp = &(*listp)->pn_next;
+
+    for (; *listp; listp = &(*listp)->pn_next) {
+        if (!Fold(cx, listp, parser, inGenexpLambda))
+            return false;
+    }
+
+    // If the last node in the list was replaced, pn_tail points into the wrong node.
+    node->pn_tail = listp;
+
+    node->checkListConsistency();
+    return true;
+}
+
+static bool
+FoldForInOrOf(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+              bool inGenexpLambda)
+{
+    MOZ_ASSERT(node->isKind(PNK_FORIN) || node->isKind(PNK_FOROF));
+    MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+    if (ParseNode*& decl = node->pn_kid1) {
+        if (!Fold(cx, &decl, parser, inGenexpLambda))
+            return false;
+    }
+
+    return Fold(cx, &node->pn_kid2, parser, inGenexpLambda) &&
+           Fold(cx, &node->pn_kid3, parser, inGenexpLambda);
+}
+
+static bool
+FoldForHead(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+            bool inGenexpLambda)
+{
+    MOZ_ASSERT(node->isKind(PNK_FORHEAD));
+    MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+    if (ParseNode*& init = node->pn_kid1) {
+        if (!Fold(cx, &init, parser, inGenexpLambda))
+            return false;
+    }
+
+    if (ParseNode*& test = node->pn_kid2) {
+        if (!FoldCondition(cx, &test, parser, inGenexpLambda))
+            return false;
+
+        if (test->isKind(PNK_TRUE)) {
+            parser.freeTree(test);
+            test = nullptr;
+        }
+    }
+
+    if (ParseNode*& update = node->pn_kid3) {
+        if (!Fold(cx, &update, parser, inGenexpLambda))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+FoldDottedProperty(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+                   bool inGenexpLambda)
+{
+    MOZ_ASSERT(node->isKind(PNK_DOT));
+    MOZ_ASSERT(node->isArity(PN_NAME));
+
+    // Iterate through a long chain of dotted property accesses to find the
+    // most-nested non-dotted property node, then fold that.
+    ParseNode** nested = &node->pn_expr;
+    while ((*nested)->isKind(PNK_DOT)) {
+        MOZ_ASSERT((*nested)->isArity(PN_NAME));
+        nested = &(*nested)->pn_expr;
+    }
+
+    return Fold(cx, nested, parser, inGenexpLambda);
+}
+
+static bool
+FoldName(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+         bool inGenexpLambda)
+{
+    MOZ_ASSERT(node->isKind(PNK_NAME));
+    MOZ_ASSERT(node->isArity(PN_NAME));
+
+    // Name nodes that are used, are in use-definition lists.  Such nodes store
+    // name analysis information and contain nothing foldable.
+    if (node->isUsed())
+        return true;
+
+    // Other names might have a foldable expression in pn_expr.
+    if (!node->pn_expr)
+        return true;
+
+    return Fold(cx, &node->pn_expr, parser, inGenexpLambda);
 }
 
 bool
-Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda,
-     SyntacticContext sc)
+Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda)
 {
     JS_CHECK_RECURSION(cx, return false);
 
     ParseNode* pn = *pnp;
-    ParseNode* pn1 = nullptr;
-    ParseNode* pn2 = nullptr;
 
     switch (pn->getKind()) {
       case PNK_NEWTARGET:
       case PNK_NOP:
       case PNK_REGEXP:
       case PNK_STRING:
       case PNK_TRUE:
       case PNK_FALSE:
@@ -1414,30 +1709,27 @@ Fold(ExclusiveContext* cx, ParseNode** p
       case PNK_TEMPLATE_STRING:
       case PNK_THIS:
       case PNK_GENERATOR:
       case PNK_EXPORT_BATCH_SPEC:
       case PNK_OBJECT_PROPERTY_NAME:
       case PNK_SUPERPROP:
       case PNK_FRESHENBLOCK:
         MOZ_ASSERT(pn->isArity(PN_NULLARY));
-        goto afterFolding;
+        return true;
 
       case PNK_TYPEOFNAME:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME));
         MOZ_ASSERT(!pn->pn_kid->maybeExpr());
         return true;
 
       case PNK_TYPEOFEXPR:
         return FoldTypeOfExpr(cx, pn, parser, inGenexpLambda);
 
-      case PNK_VOID:
-        return FoldVoid(cx, pn, parser, inGenexpLambda, sc);
-
       case PNK_DELETENAME: {
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME));
         return true;
       }
 
       case PNK_DELETEEXPR:
         return FoldDeleteExpr(cx, pn, parser, inGenexpLambda);
@@ -1471,28 +1763,31 @@ Fold(ExclusiveContext* cx, ParseNode** p
         return FoldIncrementDecrement(cx, pn, parser, inGenexpLambda);
 
       case PNK_THROW:
       case PNK_ARRAYPUSH:
       case PNK_MUTATEPROTO:
       case PNK_COMPUTED_NAME:
       case PNK_SPREAD:
       case PNK_SUPERELEM:
+      case PNK_EXPORT:
+      case PNK_EXPORT_DEFAULT:
+      case PNK_VOID:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
-        return Fold(cx, &pn->pn_kid, parser, inGenexpLambda, SyntacticContext::Other);
+        return Fold(cx, &pn->pn_kid, parser, inGenexpLambda);
 
       case PNK_SEMI:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         if (ParseNode*& expr = pn->pn_kid)
-            return Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other);
+            return Fold(cx, &expr, parser, inGenexpLambda);
         return true;
 
       case PNK_AND:
       case PNK_OR:
-        return FoldAndOr(cx, pnp, parser, inGenexpLambda, sc);
+        return FoldAndOr(cx, pnp, parser, inGenexpLambda);
 
       case PNK_FUNCTION:
         return FoldFunction(cx, pn, parser, inGenexpLambda);
 
       case PNK_SUB:
       case PNK_STAR:
       case PNK_LSH:
       case PNK_RSH:
@@ -1515,428 +1810,163 @@ Fold(ExclusiveContext* cx, ParseNode** p
       case PNK_NE:
       case PNK_LT:
       case PNK_LE:
       case PNK_GT:
       case PNK_GE:
       case PNK_INSTANCEOF:
       case PNK_IN:
       case PNK_COMMA:
+      case PNK_NEW:
       case PNK_ARRAY:
       case PNK_OBJECT:
       case PNK_ARRAYCOMP:
       case PNK_STATEMENTLIST:
       case PNK_CLASSMETHODLIST:
       case PNK_CATCHLIST:
       case PNK_TEMPLATE_STRING_LIST:
       case PNK_VAR:
       case PNK_CONST:
       case PNK_GLOBALCONST:
       case PNK_LET:
       case PNK_ARGSBODY:
       case PNK_CALLSITEOBJ:
       case PNK_EXPORT_SPEC_LIST:
       case PNK_IMPORT_SPEC_LIST:
+      case PNK_GENEXP:
         return FoldList(cx, pn, parser, inGenexpLambda);
 
       case PNK_YIELD_STAR:
         MOZ_ASSERT(pn->isArity(PN_BINARY));
         MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME));
         MOZ_ASSERT(!pn->pn_right->isAssigned());
-        return Fold(cx, &pn->pn_left, parser, inGenexpLambda, SyntacticContext::Other);
+        return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
 
       case PNK_YIELD:
         MOZ_ASSERT(pn->isArity(PN_BINARY));
         MOZ_ASSERT((pn->pn_right->isKind(PNK_NAME) && !pn->pn_right->isAssigned()) ||
                    (pn->pn_right->isKind(PNK_ASSIGN) &&
                     pn->pn_right->pn_left->isKind(PNK_NAME) &&
                     pn->pn_right->pn_right->isKind(PNK_GENERATOR)));
         if (!pn->pn_left)
             return true;
-        return Fold(cx, &pn->pn_left, parser, inGenexpLambda, SyntacticContext::Other);
+        return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
 
       case PNK_RETURN:
         return FoldReturn(cx, pn, parser, inGenexpLambda);
 
       case PNK_TRY:
         return FoldTry(cx, pn, parser, inGenexpLambda);
 
       case PNK_CATCH:
         return FoldCatch(cx, pn, parser, inGenexpLambda);
 
       case PNK_CLASS:
         return FoldClass(cx, pn, parser, inGenexpLambda);
 
-      case PNK_EXPORT:
+      case PNK_ELEM:
+        return FoldElement(cx, pnp, parser, inGenexpLambda);
+
+      case PNK_ADD:
+        return FoldAdd(cx, pnp, parser, inGenexpLambda);
+
+      case PNK_CALL:
+      case PNK_TAGGED_TEMPLATE:
+        return FoldCall(cx, pn, parser, inGenexpLambda);
+
+      case PNK_SWITCH:
+      case PNK_CASE:
+      case PNK_COLON:
       case PNK_ASSIGN:
       case PNK_ADDASSIGN:
       case PNK_SUBASSIGN:
       case PNK_BITORASSIGN:
+      case PNK_BITANDASSIGN:
       case PNK_BITXORASSIGN:
-      case PNK_BITANDASSIGN:
       case PNK_LSHASSIGN:
       case PNK_RSHASSIGN:
       case PNK_URSHASSIGN:
-      case PNK_MULASSIGN:
       case PNK_DIVASSIGN:
       case PNK_MODASSIGN:
+      case PNK_MULASSIGN:
       case PNK_POWASSIGN:
-      case PNK_ELEM:
-      case PNK_COLON:
-      case PNK_CASE:
+      case PNK_IMPORT:
+      case PNK_EXPORT_FROM:
       case PNK_SHORTHAND:
-      case PNK_DOWHILE:
-      case PNK_WHILE:
-      case PNK_SWITCH:
       case PNK_LETBLOCK:
       case PNK_FOR:
       case PNK_CLASSMETHOD:
-      case PNK_WITH:
+      case PNK_IMPORT_SPEC:
+      case PNK_EXPORT_SPEC:
+        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        return Fold(cx, &pn->pn_left, parser, inGenexpLambda) &&
+               Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+
       case PNK_CLASSNAMES:
+        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        if (ParseNode*& outerBinding = pn->pn_left) {
+            if (!Fold(cx, &outerBinding, parser, inGenexpLambda))
+                return false;
+        }
+        return Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+
+      case PNK_DOWHILE:
+        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        return Fold(cx, &pn->pn_left, parser, inGenexpLambda) &&
+               FoldCondition(cx, &pn->pn_right, parser, inGenexpLambda);
+
+      case PNK_WHILE:
+        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        return FoldCondition(cx, &pn->pn_left, parser, inGenexpLambda) &&
+               Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+
       case PNK_DEFAULT:
-      case PNK_IMPORT:
-      case PNK_EXPORT_FROM:
-      case PNK_EXPORT_DEFAULT:
+        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(!pn->pn_left);
+        MOZ_ASSERT(pn->pn_right->isKind(PNK_STATEMENTLIST));
+        return Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+
+      case PNK_WITH:
+        MOZ_ASSERT(pn->isArity(PN_BINARY_OBJ));
+        return Fold(cx, &pn->pn_left, parser, inGenexpLambda) &&
+               Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+
       case PNK_FORIN:
       case PNK_FOROF:
+        return FoldForInOrOf(cx, pn, parser, inGenexpLambda);
+
       case PNK_FORHEAD:
-      case PNK_ADD:
-      case PNK_NEW:
-      case PNK_CALL:
-      case PNK_GENEXP:
-      case PNK_TAGGED_TEMPLATE:
+        return FoldForHead(cx, pn, parser, inGenexpLambda);
+
       case PNK_LABEL:
+        MOZ_ASSERT(pn->isArity(PN_NAME));
+        return Fold(cx, &pn->pn_expr, parser, inGenexpLambda);
+
       case PNK_DOT:
+        return FoldDottedProperty(cx, pn, parser, inGenexpLambda);
+
       case PNK_LEXICALSCOPE:
+        MOZ_ASSERT(pn->isArity(PN_NAME));
+        if (!pn->pn_expr)
+            return true;
+        return Fold(cx, &pn->pn_expr, parser, inGenexpLambda);
+
       case PNK_NAME:
-      case PNK_EXPORT_SPEC:
-      case PNK_IMPORT_SPEC:
-        MOZ_ASSERT(!pn->isArity(PN_CODE), "only functions are code nodes");
-        break; // for now
+        return FoldName(cx, pn, parser, inGenexpLambda);
 
       case PNK_LIMIT: // invalid sentinel value
         MOZ_CRASH("invalid node kind");
     }
 
-    // First, recursively fold constants on the children of this node.
-    switch (pn->getArity()) {
-      case PN_CODE:
-        MOZ_ASSERT(pn->isKind(PNK_FUNCTION));
-        MOZ_CRASH("should have been handled above");
-
-      case PN_LIST:
-      {
-        // Don't fold a parenthesized call expression. See bug 537673.
-        ParseNode** listp = &pn->pn_head;
-        if ((pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE)) && (*listp)->isInParens())
-            listp = &(*listp)->pn_next;
-
-        for (; *listp; listp = &(*listp)->pn_next) {
-            if (!Fold(cx, listp, parser, inGenexpLambda, SyntacticContext::Other))
-                return false;
-        }
-
-        // If the last node in the list was replaced, pn_tail points into the wrong node.
-        pn->pn_tail = listp;
-
-        // Save the list head in pn1 for later use.
-        pn1 = pn->pn_head;
-        pn2 = nullptr;
-        break;
-      }
-
-      case PN_TERNARY:
-        MOZ_ASSERT(!pn->isKind(PNK_CONDITIONAL),
-                   "should be skipping this above");
-        MOZ_ASSERT(!pn->isKind(PNK_IF),
-                   "should be skipping this above");
-        /* Any kid may be null (e.g. for (;;)). */
-        if (pn->pn_kid1) {
-            if (!Fold(cx, &pn->pn_kid1, parser, inGenexpLambda, SyntacticContext::Other))
-                return false;
-        }
-        pn1 = pn->pn_kid1;
-
-        if (pn->pn_kid2) {
-            if (!Fold(cx, &pn->pn_kid2, parser, inGenexpLambda, condIf(pn, PNK_FORHEAD)))
-                return false;
-            if (pn->isKind(PNK_FORHEAD) && pn->pn_kid2->isKind(PNK_TRUE)) {
-                parser.freeTree(pn->pn_kid2);
-                pn->pn_kid2 = nullptr;
-            }
-        }
-        pn2 = pn->pn_kid2;
-
-        if (pn->pn_kid3) {
-            if (!Fold(cx, &pn->pn_kid3, parser, inGenexpLambda, SyntacticContext::Other))
-                return false;
-        }
-        break;
-
-      case PN_BINARY:
-      case PN_BINARY_OBJ:
-        /* First kid may be null (for default case in switch). */
-        if (pn->pn_left) {
-            if (!Fold(cx, &pn->pn_left, parser, inGenexpLambda, condIf(pn, PNK_WHILE)))
-                return false;
-        }
-        /* Second kid may be null (for return in non-generator). */
-        if (pn->pn_right) {
-            if (!Fold(cx, &pn->pn_right, parser, inGenexpLambda, condIf(pn, PNK_DOWHILE)))
-                return false;
-        }
-        pn1 = pn->pn_left;
-        pn2 = pn->pn_right;
-        break;
-
-      case PN_UNARY:
-        MOZ_ASSERT(!IsDeleteKind(pn->getKind()),
-                   "should have been handled above");
-        if (pn->pn_kid) {
-            if (!Fold(cx, &pn->pn_kid, parser, inGenexpLambda, SyntacticContext::Other))
-                return false;
-        }
-        pn1 = pn->pn_kid;
-        break;
-
-      case PN_NAME:
-        /*
-         * Skip pn1 down along a chain of dotted member expressions to avoid
-         * excessive recursion.  Our only goal here is to fold constants (if
-         * any) in the primary expression operand to the left of the first
-         * dot in the chain.
-         */
-        if (!pn->isUsed()) {
-            ParseNode** lhsp = &pn->pn_expr;
-            while (*lhsp && (*lhsp)->isArity(PN_NAME) && !(*lhsp)->isUsed())
-                lhsp = &(*lhsp)->pn_expr;
-            if (*lhsp && !Fold(cx, lhsp, parser, inGenexpLambda, SyntacticContext::Other))
-                return false;
-            pn1 = *lhsp;
-        }
-        break;
-
-      case PN_NULLARY:
-        break;
-    }
-
-    // The immediate child of a PNK_DELETE* node should not be replaced
-    // with node indicating a different syntactic form; |delete x| is not
-    // the same as |delete (true && x)|. See bug 888002.
-    //
-    // pn is the immediate child in question. Its descendants were already
-    // constant-folded above, so we're done.
-    if (sc == SyntacticContext::Delete)
-        return true;
-
-    switch (pn->getKind()) {
-      case PNK_ADD: {
-        MOZ_ASSERT(pn->isArity(PN_LIST));
-
-        bool folded = false;
-
-        pn2 = pn1->pn_next;
-        if (pn1->isKind(PNK_NUMBER)) {
-            // Fold addition of numeric literals: (1 + 2 + x === 3 + x).
-            // Note that we can only do this the front of the list:
-            // (x + 1 + 2 !== x + 3) when x is a string.
-            while (pn2 && pn2->isKind(PNK_NUMBER)) {
-                pn1->pn_dval += pn2->pn_dval;
-                pn1->pn_next = pn2->pn_next;
-                parser.freeTree(pn2);
-                pn2 = pn1->pn_next;
-                pn->pn_count--;
-                folded = true;
-            }
-        }
-
-        // Now search for adjacent pairs of literals to fold for string
-        // concatenation.
-        //
-        // isStringConcat is true if we know the operation we're looking at
-        // will be string concatenation at runtime.  As soon as we see a
-        // string, we know that every addition to the right of it will be
-        // string concatenation, even if both operands are numbers:
-        // ("s" + x + 1 + 2 === "s" + x + "12").
-        //
-        bool isStringConcat = false;
-        RootedString foldedStr(cx);
-
-        // (number + string) is definitely concatenation, but only at the
-        // front of the list: (x + 1 + "2" !== x + "12") when x is a
-        // number.
-        if (pn1->isKind(PNK_NUMBER) && pn2 && pn2->isKind(PNK_STRING))
-            isStringConcat = true;
-
-        while (pn2) {
-            isStringConcat = isStringConcat || pn1->isKind(PNK_STRING);
-
-            if (isStringConcat &&
-                (pn1->isKind(PNK_STRING) || pn1->isKind(PNK_NUMBER)) &&
-                (pn2->isKind(PNK_STRING) || pn2->isKind(PNK_NUMBER)))
-            {
-                // Fold string concatenation of literals.
-                if (pn1->isKind(PNK_NUMBER) && !FoldType(cx, pn1, PNK_STRING))
-                    return false;
-                if (pn2->isKind(PNK_NUMBER) && !FoldType(cx, pn2, PNK_STRING))
-                    return false;
-                if (!foldedStr)
-                    foldedStr = pn1->pn_atom;
-                RootedString right(cx, pn2->pn_atom);
-                foldedStr = ConcatStrings<CanGC>(cx, foldedStr, right);
-                if (!foldedStr)
-                    return false;
-                pn1->pn_next = pn2->pn_next;
-                parser.freeTree(pn2);
-                pn2 = pn1->pn_next;
-                pn->pn_count--;
-                folded = true;
-            } else {
-                if (foldedStr) {
-                    // Convert the rope of folded strings into an Atom.
-                    pn1->pn_atom = AtomizeString(cx, foldedStr);
-                    if (!pn1->pn_atom)
-                        return false;
-                    foldedStr = nullptr;
-                }
-                pn1 = pn2;
-                pn2 = pn2->pn_next;
-            }
-        }
-
-        if (foldedStr) {
-            // Convert the rope of folded strings into an Atom.
-            pn1->pn_atom = AtomizeString(cx, foldedStr);
-            if (!pn1->pn_atom)
-                return false;
-        }
-
-        if (folded) {
-            if (pn->pn_count == 1) {
-                // We reduced the list to one constant. There is no
-                // addition anymore. Replace the PNK_ADD node with the
-                // single PNK_STRING or PNK_NUMBER node.
-                ReplaceNode(pnp, pn1);
-                pn = pn1;
-            } else if (!pn2) {
-                pn->pn_tail = &pn1->pn_next;
-            }
-        }
-
-        break;
-      }
-
-      case PNK_TYPEOFNAME:
-      case PNK_TYPEOFEXPR:
-      case PNK_VOID:
-      case PNK_NOT:
-      case PNK_BITNOT:
-      case PNK_POS:
-      case PNK_NEG:
-      case PNK_CONDITIONAL:
-      case PNK_IF:
-      case PNK_AND:
-      case PNK_OR:
-      case PNK_SUB:
-      case PNK_STAR:
-      case PNK_LSH:
-      case PNK_RSH:
-      case PNK_URSH:
-      case PNK_DIV:
-      case PNK_MOD:
-      case PNK_POW:
-        MOZ_CRASH("should have been fully handled above");
-
-      case PNK_ELEM: {
-        // An indexed expression, pn1[pn2]. A few cases can be improved.
-        PropertyName* name = nullptr;
-        if (pn2->isKind(PNK_STRING)) {
-            JSAtom* atom = pn2->pn_atom;
-            uint32_t index;
-
-            if (atom->isIndex(&index)) {
-                // Optimization 1: We have something like pn1["100"]. This is
-                // equivalent to pn1[100] which is faster.
-                pn2->setKind(PNK_NUMBER);
-                pn2->setOp(JSOP_DOUBLE);
-                pn2->pn_dval = index;
-            } else {
-                name = atom->asPropertyName();
-            }
-        } else if (pn2->isKind(PNK_NUMBER)) {
-            double number = pn2->pn_dval;
-            if (number != ToUint32(number)) {
-                // Optimization 2: We have something like pn1[3.14]. The number
-                // is not an array index. This is equivalent to pn1["3.14"]
-                // which enables optimization 3 below.
-                JSAtom* atom = ToAtom<NoGC>(cx, DoubleValue(number));
-                if (!atom)
-                    return false;
-                name = atom->asPropertyName();
-            }
-        }
-
-        if (name && NameToId(name) == IdToTypeId(NameToId(name))) {
-            // Optimization 3: We have pn1["foo"] where foo is not an index.
-            // Convert to a property access (like pn1.foo) which we optimize
-            // better downstream. Don't bother with this for names which TI
-            // considers to be indexes, to simplify downstream analysis.
-            ParseNode* expr = parser.handler.newPropertyAccess(pn->pn_left, name, pn->pn_pos.end);
-            if (!expr)
-                return false;
-            expr->setInParens(pn->isInParens());
-            ReplaceNode(pnp, expr);
-
-            // Supposing we're replacing |obj["prop"]| with |obj.prop|, we now
-            // can free the |"prop"| node and |obj["prop"]| nodes -- but not
-            // the |obj| node now a sub-node of |expr|.  Mutate |pn| into a
-            // node with |"prop"| as its child so that our |pn| doesn't have a
-            // necessarily-weird structure (say, by nulling out |pn->pn_left|
-            // only) that would fail AST sanity assertions performed by
-            // |parser.freeTree(pn)|.
-            pn->setKind(PNK_TYPEOFEXPR);
-            pn->setArity(PN_UNARY);
-            pn->pn_kid = pn2;
-            parser.freeTree(pn);
-
-            pn = expr;
-        }
-        break;
-      }
-
-      default:;
-    }
-
-  afterFolding:
-    if (sc == SyntacticContext::Condition) {
-        Truthiness t = Boolish(pn);
-        if (t != Unknown) {
-            /*
-             * We can turn function nodes into constant nodes here, but mutating function
-             * nodes is tricky --- in particular, mutating a function node that appears on
-             * a method list corrupts the method list. However, methods are M's in
-             * statements of the form 'this.foo = M;', which we never fold, so we're okay.
-             */
-            parser.prepareNodeForMutation(pn);
-            if (t == Truthy) {
-                pn->setKind(PNK_TRUE);
-                pn->setOp(JSOP_TRUE);
-            } else {
-                pn->setKind(PNK_FALSE);
-                pn->setOp(JSOP_FALSE);
-            }
-            pn->setArity(PN_NULLARY);
-        }
-    }
-
-    return true;
+    MOZ_CRASH("shouldn't reach here");
+    return false;
 }
 
 bool
 frontend::FoldConstants(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>* parser)
 {
     // Don't constant-fold inside "use asm" code, as this could create a parse
     // tree that doesn't type-check as asm.js.
     if (parser->pc->useAsmOrInsideUseAsm())
         return true;
 
-    return Fold(cx, pnp, *parser, false, SyntacticContext::Other);
+    return Fold(cx, pnp, *parser, false);
 }
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -5700,17 +5700,19 @@ Parser<ParseHandler>::yieldExpression(In
           case TOK_SEMI:
           case TOK_RC:
           case TOK_RB:
           case TOK_RP:
           case TOK_COLON:
           case TOK_COMMA:
             // No value.
             exprNode = null();
-            tokenStream.addModifierException(TokenStream::NoneIsOperand);
+            tokenStream.addModifierException((tt == TOK_EOL || tt == TOK_EOF)
+                                             ? TokenStream::NoneIsOperandYieldEOL
+                                             : TokenStream::NoneIsOperand);
             break;
           case TOK_MUL:
             kind = PNK_YIELD_STAR;
             tokenStream.consumeKnownToken(TOK_MUL, TokenStream::Operand);
             // Fall through.
           default:
             exprNode = assignExpr(inHandling, YieldIsKeyword);
             if (!exprNode)
@@ -5763,17 +5765,19 @@ Parser<ParseHandler>::yieldExpression(In
           case TOK_SEMI:
           case TOK_RC:
           case TOK_RB:
           case TOK_RP:
           case TOK_COLON:
           case TOK_COMMA:
             // No value.
             exprNode = null();
-            tokenStream.addModifierException(TokenStream::NoneIsOperand);
+            tokenStream.addModifierException((tt == TOK_EOL || tt == TOK_EOF)
+                                             ? TokenStream::NoneIsOperandYieldEOL
+                                             : TokenStream::NoneIsOperand);
             break;
           default:
             exprNode = assignExpr(inHandling, YieldIsKeyword);
             if (!exprNode)
                 return null();
         }
 
         return newYieldExpression(begin, exprNode);
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1645,17 +1645,17 @@ TokenStream::getTokenInternal(TokenKind*
 
     flags.isDirtyLine = true;
     tp->pos.end = userbuf.offset();
 #ifdef DEBUG
     // Save the modifier used to get this token, so that if an ungetToken()
     // occurs and then the token is re-gotten (or peeked, etc.), we can assert
     // that both gets have used the same modifiers.
     tp->modifier = modifier;
-    tp->modifierExceptions = NoException;
+    tp->modifierException = NoException;
 #endif
     MOZ_ASSERT(IsTokenSane(tp));
     *ttp = tp->type;
     return true;
 
   error:
     if (flags.hitOOM)
         return reportError(JSMSG_OUT_OF_MEMORY);
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -112,16 +112,39 @@ struct Token
         // or more template literal characters, and ending with either "${" or
         // the end of the template literal.  For example:
         //
         //   var entity = "world";
         //   var s = `Hello ${entity}!`;
         //                          ^ TemplateTail context
         TemplateTail,
     };
+    enum ModifierException
+    {
+        NoException,
+
+        // If an yield expression operand is omitted and yield expression is
+        // followed by non-EOL, the next token is already gotten with Operand,
+        // but we expect operator (None).
+        NoneIsOperand,
+
+        // If an yield expression operand is omitted and yield expression is
+        // followed by EOL, the next token is already gotten with Operand, and
+        // we expect Operand in next statement, but MatchOrInsertSemicolon
+        // after expression statement expects operator (None).
+        NoneIsOperandYieldEOL,
+
+        // If a semicolon is inserted automatically, the next token is already
+        // gotten with None, but we expect Operand.
+        OperandIsNone,
+
+        // If name of method definition is `get` or `set`, the next token is
+        // already gotten with KeywordIsName, but we expect None.
+        NoneIsKeywordIsName,
+    };
     friend class TokenStream;
 
   public:
     TokenKind           type;           // char value or above enumerator
     TokenPos            pos;            // token position in file
     union {
       private:
         friend struct Token;
@@ -131,17 +154,17 @@ struct Token
             double      value;          // floating point number
             DecimalPoint decimalPoint;  // literal contains '.'
         } number;
         RegExpFlag      reflags;        // regexp flags; use tokenbuf to access
                                         //   regexp chars
     } u;
 #ifdef DEBUG
     Modifier modifier;                  // Modifier used to get this token
-    uint8_t modifierExceptions;         // Bitwise OR of modifier exceptions
+    ModifierException modifierException; // Exception for this modifier
 #endif
 
     // This constructor is necessary only for MSVC 2013 and how it compiles the
     // initialization of TokenStream::tokens.  That field is initialized as
     // tokens() in the constructor init-list.  This *should* zero the entire
     // array, then (because Token has a non-trivial constructor, because
     // TokenPos has a user-provided constructor) call the implicit Token
     // constructor on each element, which would call the TokenPos constructor
@@ -406,58 +429,68 @@ class MOZ_STACK_CLASS TokenStream
 
   public:
     typedef Token::Modifier Modifier;
     static MOZ_CONSTEXPR_VAR Modifier None = Token::None;
     static MOZ_CONSTEXPR_VAR Modifier Operand = Token::Operand;
     static MOZ_CONSTEXPR_VAR Modifier KeywordIsName = Token::KeywordIsName;
     static MOZ_CONSTEXPR_VAR Modifier TemplateTail = Token::TemplateTail;
 
-    enum ModifierException
-    {
-        NoException = 0x00,
-
-        // If a semicolon is inserted automatically, the next token is already
-        // gotten with None, but we expect Operand.
-        NoneIsOperand = 0x01,
-
-        // If an yield expression operand is omitted, the next token is already
-        // gotten with Operand, but we expect operator (None).
-        OperandIsNone = 0x02,
-
-        // If name of method definition is `get` or `set`, the next token is
-        // already gotten with KeywordIsName, but we expect None.
-        NoneIsKeywordIsName = 0x04,
-    };
+    typedef Token::ModifierException ModifierException;
+    static MOZ_CONSTEXPR_VAR ModifierException NoException = Token::NoException;
+    static MOZ_CONSTEXPR_VAR ModifierException NoneIsOperand = Token::NoneIsOperand;
+    static MOZ_CONSTEXPR_VAR ModifierException NoneIsOperandYieldEOL = Token::NoneIsOperandYieldEOL;
+    static MOZ_CONSTEXPR_VAR ModifierException OperandIsNone = Token::OperandIsNone;
+    static MOZ_CONSTEXPR_VAR ModifierException NoneIsKeywordIsName = Token::NoneIsKeywordIsName;
 
     void addModifierException(ModifierException modifierException) {
 #ifdef DEBUG
         const Token& next = nextToken();
+        if (next.modifierException == NoneIsOperand ||
+            next.modifierException == NoneIsOperandYieldEOL)
+        {
+            // Token after yield expression without operand already has
+            // NoneIsOperand or NoneIsOperandYieldEOL exception.
+            MOZ_ASSERT(modifierException == OperandIsNone);
+            if (next.modifierException == NoneIsOperand)
+                MOZ_ASSERT(next.type != TOK_DIV && next.type != TOK_REGEXP,
+                           "next token requires contextual specifier to be parsed unambiguously");
+            else
+                MOZ_ASSERT(next.type != TOK_DIV,
+                           "next token requires contextual specifier to be parsed unambiguously");
+
+            // Do not update modifierException.
+            return;
+        }
+
+        MOZ_ASSERT(next.modifierException == NoException);
         switch (modifierException) {
           case NoneIsOperand:
             MOZ_ASSERT(next.modifier == Operand);
             MOZ_ASSERT(next.type != TOK_DIV && next.type != TOK_REGEXP,
                        "next token requires contextual specifier to be parsed unambiguously");
             break;
+          case NoneIsOperandYieldEOL:
+            MOZ_ASSERT(next.modifier == Operand);
+            MOZ_ASSERT(next.type != TOK_DIV,
+                       "next token requires contextual specifier to be parsed unambiguously");
+            break;
           case OperandIsNone:
-            // Non-Operand token after yield/continue/break already has
-            // NoneIsOperand exception.
-            MOZ_ASSERT(next.modifier == None ||
-                       ((next.modifierExceptions & NoneIsOperand) && next.modifier == Operand));
+            MOZ_ASSERT(next.modifier == None);
             MOZ_ASSERT(next.type != TOK_DIV && next.type != TOK_REGEXP,
                        "next token requires contextual specifier to be parsed unambiguously");
             break;
           case NoneIsKeywordIsName:
             MOZ_ASSERT(next.modifier == KeywordIsName);
             MOZ_ASSERT(next.type != TOK_NAME);
             break;
           default:
             MOZ_CRASH("unexpected modifier exception");
         }
-        tokens[(cursor + 1) & ntokensMask].modifierExceptions |= modifierException;
+        tokens[(cursor + 1) & ntokensMask].modifierException = modifierException;
 #endif
     }
 
     bool hasLookahead() { return lookahead > 0; }
 
     Modifier getLookaheadModifier() {
 #ifdef DEBUG
         return nextToken().modifier;
@@ -468,29 +501,31 @@ class MOZ_STACK_CLASS TokenStream
 
     void
     verifyConsistentModifier(Modifier modifier, Token lookaheadToken) {
 #ifdef DEBUG
         // Easy case: modifiers match.
         if (modifier == lookaheadToken.modifier)
             return;
 
-        if (lookaheadToken.modifierExceptions & OperandIsNone) {
+        if (lookaheadToken.modifierException == OperandIsNone) {
             // getToken(Operand) permissibly following getToken().
             if (modifier == Operand && lookaheadToken.modifier == None)
                 return;
         }
 
-        if (lookaheadToken.modifierExceptions & NoneIsOperand) {
+        if (lookaheadToken.modifierException == NoneIsOperand ||
+            lookaheadToken.modifierException == NoneIsOperandYieldEOL)
+        {
             // getToken() permissibly following getToken(Operand).
             if (modifier == None && lookaheadToken.modifier == Operand)
                 return;
         }
 
-        if (lookaheadToken.modifierExceptions & NoneIsKeywordIsName) {
+        if (lookaheadToken.modifierException == NoneIsKeywordIsName) {
             // getToken() permissibly following getToken(KeywordIsName).
             if (modifier == None && lookaheadToken.modifier == KeywordIsName)
                 return;
         }
 
         MOZ_ASSERT_UNREACHABLE("this token was previously looked up with a "
                                "different modifier, potentially making "
                                "tokenization non-deterministic");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1172503-2.js
@@ -0,0 +1,10 @@
+var n = 0;
+this.__proto__ = new Proxy({}, {
+    has: function () {
+        if (++n === 2)
+            return false;
+        a = 0;
+    }
+});
+a = 0;
+assertEq(a, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1172503.js
@@ -0,0 +1,9 @@
+// |jit-test| error: m is not defined
+this.__proto__ = Proxy.create({
+  has:function(){
+    try {
+      aa0 = Function(undefined);
+    } catch (aa) {}
+  }
+});
+m();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1189744.js
@@ -0,0 +1,11 @@
+var obj;
+for (var i = 0; i < 100; i++)
+    obj = {a: 7, b: 13, c: 42, d: 0};
+
+Object.defineProperty(obj, "x", {
+    get: function () { return 3; }
+});
+obj.__ob__ = 17;
+
+Object.defineProperty(obj, "c", {value: 8, writable: true});
+assertEq(obj.__ob__, 17);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/oomInWeakMap.js
@@ -0,0 +1,7 @@
+// |jit-test| --no-ggc; allow-unhandlable-oom; --no-threads
+
+load(libdir + 'oomTest.js');
+oomTest(function () {
+    eval(`var wm = new WeakMap();
+         wm.set({}, 'FOO').get(false);`);
+});
--- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js
+++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js
@@ -470,17 +470,18 @@ function rfrom_char_code_non_ascii(i) {
     return i;
 }
 
 var uceFault_pow_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_pow_number'));
 function rpow_number(i) {
     var x = Math.pow(i, 3.14159);
     if (uceFault_pow_number(i) || uceFault_pow_number(i))
         assertEq(x, Math.pow(99, 3.14159));
-    assertRecoveredOnBailout(x, true);
+    // POW recovery temporarily disabled. See bug 1188586.
+    assertRecoveredOnBailout(x, false);
     return i;
 }
 
 var uceFault_pow_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_pow_object'));
 function rpow_object(i) {
     var t = i;
     var o = { valueOf: function () { return t; } };
     var x = Math.pow(o, 3.14159); /* computed with t == i, not 1.5 */
--- a/js/src/jit-test/tests/parser/yield-without-operand.js
+++ b/js/src/jit-test/tests/parser/yield-without-operand.js
@@ -1,18 +1,24 @@
 // yield without an operand is fine and dandy.
 
 load(libdir + "asserts.js");
 
 assertNoWarning(() => Function("yield"), SyntaxError,
                 "yield followed by EOF is fine");
 assertNoWarning(() => Function("yield;"), SyntaxError,
                 "yield followed by semicolon is fine");
+assertNoWarning(() => Function("yield\n"), SyntaxError,
+                "yield followed by newline is fine");
 assertNoWarning(() => Function("yield\n  print('ok');"), SyntaxError,
-                "yield followed by newline is fine");
+                "yield followed by newline and statement is fine");
+assertNoWarning(() => Function("yield\n  /x/;"), SyntaxError,
+                "yield followed by newline and regexp is fine");
+assertThrowsInstanceOf(() => Function("yield\n  /"), SyntaxError,
+                       "yield followed by newline and slash is fine");
 
 assertNoWarning(() => eval("(function () { yield; })"), SyntaxError,
                 "yield followed by semicolon in eval code is fine");
 assertNoWarning(() => eval("(function () { yield })"), SyntaxError,
                 "yield followed by } in eval code is fine");
 
 assertNoWarning(() => Function("yield 0;"),
                 "yield with an operand should be fine");
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -5944,17 +5944,18 @@ class MPow
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
     bool possiblyCalls() const override {
         return true;
     }
     bool writeRecoverData(CompactBufferWriter& writer) const override;
     bool canRecoverOnBailout() const override {
-        return true;
+        // Temporarily disable recovery to relieve fuzzer pressure. See bug 1188586.
+        return false;
     }
 
     ALLOW_CLONE(MPow)
 };
 
 // Inline implementation of Math.pow(x, 0.5), which subtly differs from Math.sqrt(x).
 class MPowHalf
   : public MUnaryInstruction,
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -1016,21 +1016,20 @@ ArrayJoinKernel(JSContext* cx, Separator
             if (!CheckForInterrupt(cx))
                 return false;
 
             bool hole;
             if (!GetElement(cx, obj, i, &hole, &v))
                 return false;
             if (!hole && !v.isNullOrUndefined()) {
                 if (Locale) {
-                    JSObject* robj = ToObject(cx, v);
-                    if (!robj)
+                    RootedValue fun(cx);
+                    if (!GetProperty(cx, v, cx->names().toLocaleString, &fun))
                         return false;
-                    RootedId id(cx, NameToId(cx->names().toLocaleString));
-                    if (!robj->callMethod(cx, id, 0, nullptr, &v))
+                    if (!Invoke(cx, v, fun, 0, nullptr, &v))
                         return false;
                 }
                 if (!ValueToStringBuffer(cx, v, sb))
                     return false;
             }
 
             if (++i != length && !sepOp(cx, sb))
                 return false;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2581,18 +2581,20 @@ js::GetOwnPropertyDescriptor(JSContext* 
             desc.setSetterObject(nullptr);
             desc.attributesRef() |= JSPROP_SETTER;
         }
 
         desc.value().setUndefined();
     } else {
         // This is either a straight-up data property or (rarely) a
         // property with a JSGetterOp/JSSetterOp. The latter must be
-        // reported to the caller as a plain data property, so don't
-        // populate desc.getter/setter, and mask away the SHARED bit.
+        // reported to the caller as a plain data property, so clear
+        // desc.getter/setter, and mask away the SHARED bit.
+        desc.setGetter(nullptr);
+        desc.setSetter(nullptr);
         desc.attributesRef() &= ~JSPROP_SHARED;
 
         if (IsImplicitDenseOrTypedArrayElement(shape)) {
             desc.value().set(nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id)));
         } else {
             if (!NativeGetExistingProperty(cx, nobj, nobj, shape, desc.value()))
                 return false;
         }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -999,17 +999,18 @@ GetObjectClassName(JSContext* cx, Handle
  *
  * As a result, we have calls to these three "substitute-this-object-for-that-
  * object" functions sprinkled at apparently arbitrary (but actually *very*
  * carefully and nervously selected) places throughout the engine and indeed
  * the universe.
  */
 
 /*
- * If obj a WindowProxy, return its current inner Window. Otherwise return obj.
+ * If obj is a WindowProxy, return its current inner Window. Otherwise return
+ * obj. This function can't fail and never returns nullptr.
  *
  * GetInnerObject is called when we need a scope chain; you never want a
  * WindowProxy on a scope chain.
  *
  * It's also called in a few places where an object comes in from script, and
  * the user probably intends to operate on the Window, not the
  * WindowProxy. Object.prototype.watch and various Debugger features do
  * this. (Users can't simply pass the Window, because the Window isn't exposed
@@ -1022,16 +1023,17 @@ GetInnerObject(JSObject* obj)
         JS::AutoSuppressGCAnalysis nogc;
         return op(obj);
     }
     return obj;
 }
 
 /*
  * If obj is a Window object, return the WindowProxy. Otherwise return obj.
+ * This function can't fail; it never sets an exception or returns nullptr.
  *
  * This must be called before passing an object to script, if the object might
  * be a Window. (But usually those cases involve scope objects, and for those,
  * it is better to call GetThisObject instead.)
  */
 inline JSObject*
 GetOuterObject(JSContext* cx, HandleObject obj)
 {
--- a/js/src/jsweakmap.cpp
+++ b/js/src/jsweakmap.cpp
@@ -350,24 +350,25 @@ WeakMapPostWriteBarrier(JSRuntime* rt, O
 }
 
 static MOZ_ALWAYS_INLINE bool
 SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
                         HandleObject key, HandleValue value)
 {
     ObjectValueMap* map = mapObj->getMap();
     if (!map) {
-        map = cx->new_<ObjectValueMap>(cx, mapObj.get());
-        if (!map)
+        AutoInitGCManagedObject<ObjectValueMap> newMap(
+            cx->make_unique<ObjectValueMap>(cx, mapObj.get()));
+        if (!newMap)
             return false;
-        if (!map->init()) {
-            js_delete(map);
+        if (!newMap->init()) {
             JS_ReportOutOfMemory(cx);
             return false;
         }
+        map = newMap.release();
         mapObj->setPrivate(map);
     }
 
     // Preserve wrapped native keys to prevent wrapper optimization.
     if (!TryPreserveReflector(cx, key))
         return false;
 
     if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) {
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -83,44 +83,32 @@ js::assertEnteredPolicy(JSContext* cx, J
     MOZ_ASSERT(proxy->is<ProxyObject>());
     MOZ_ASSERT(cx->runtime()->enteredPolicy);
     MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredProxy->get() == proxy);
     MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredId->get() == id);
     MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredAction & act);
 }
 #endif
 
-#define INVOKE_ON_PROTOTYPE(cx, handler, proxy, protoCall)                   \
-    JS_BEGIN_MACRO                                                           \
-        RootedObject proto(cx);                                              \
-        if (!GetPrototype(cx, proxy, &proto))                                \
-            return false;                                                    \
-        if (!proto)                                                          \
-            return true;                                                     \
-        assertSameCompartment(cx, proxy, proto);                             \
-        return protoCall;                                                    \
-    JS_END_MACRO                                                             \
-
 bool
 Proxy::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                              MutableHandle<PropertyDescriptor> desc)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     desc.object().set(nullptr); // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true);
     if (!policy.allowed())
         return policy.returnValue();
-    if (!handler->hasPrototype())
-        return handler->getPropertyDescriptor(cx, proxy, id, desc);
-    if (!handler->getOwnPropertyDescriptor(cx, proxy, id, desc))
-        return false;
-    if (desc.object())
-        return true;
-    INVOKE_ON_PROTOTYPE(cx, handler, proxy, GetPropertyDescriptor(cx, proto, id, desc));
+
+    // Special case. See the comment on BaseProxyHandler::mHasPrototype.
+    if (handler->hasPrototype())
+        return handler->BaseProxyHandler::getPropertyDescriptor(cx, proxy, id, desc);
+
+    return handler->getPropertyDescriptor(cx, proxy, id, desc);
 }
 
 bool
 Proxy::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                                 MutableHandle<PropertyDescriptor> desc)
 {
     JS_CHECK_RECURSION(cx, return false);
 
@@ -236,60 +224,87 @@ bool
 Proxy::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     *bp = false; // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
-    if (!handler->hasPrototype())
-        return handler->has(cx, proxy, id, bp);
-    if (!handler->hasOwn(cx, proxy, id, bp))
-        return false;
-    if (*bp)
-        return true;
-    bool Bp;
-    INVOKE_ON_PROTOTYPE(cx, handler, proxy,
-                        JS_HasPropertyById(cx, proto, id, &Bp) &&
-                        ((*bp = Bp) || true));
+
+    if (handler->hasPrototype()) {
+        if (!handler->hasOwn(cx, proxy, id, bp))
+            return false;
+        if (*bp)
+            return true;
+
+        RootedObject proto(cx);
+        if (!GetPrototype(cx, proxy, &proto))
+            return false;
+        if (!proto)
+            return true;
+
+        return HasProperty(cx, proto, id, bp);
+    }
+
+    return handler->has(cx, proxy, id, bp);
 }
 
 bool
 Proxy::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     *bp = false; // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
     return handler->hasOwn(cx, proxy, id, bp);
 }
 
+static Value
+OuterizeValue(JSContext* cx, HandleValue v)
+{
+    if (v.isObject()) {
+        RootedObject obj(cx, &v.toObject());
+        return ObjectValue(*GetOuterObject(cx, obj));
+    }
+    return v;
+}
+
 bool
-Proxy::get(JSContext* cx, HandleObject proxy, HandleObject receiver, HandleId id,
+Proxy::get(JSContext* cx, HandleObject proxy, HandleObject receiver_, HandleId id,
            MutableHandleValue vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     vp.setUndefined(); // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
-    bool own;
-    if (!handler->hasPrototype()) {
-        own = true;
-    } else {
+
+    // Outerize the receiver. Proxy handlers shouldn't have to know about
+    // the Window/WindowProxy distinction.
+    RootedObject receiver(cx, GetOuterObject(cx, receiver_));
+
+    if (handler->hasPrototype()) {
+        bool own;
         if (!handler->hasOwn(cx, proxy, id, &own))
             return false;
+        if (!own) {
+            RootedObject proto(cx);
+            if (!GetPrototype(cx, proxy, &proto))
+                return false;
+            if (!proto)
+                return true;
+            return GetProperty(cx, proto, receiver, id, vp);
+        }
     }
-    if (own)
-        return handler->get(cx, proxy, receiver, id, vp);
-    INVOKE_ON_PROTOTYPE(cx, handler, proxy, GetProperty(cx, proto, receiver, id, vp));
+
+    return handler->get(cx, proxy, receiver, id, vp);
 }
 
 bool
 Proxy::callProp(JSContext* cx, HandleObject proxy, HandleObject receiver, HandleId id,
                 MutableHandleValue vp)
 {
     // The inline caches need an access point for JSOP_CALLPROP sites that accounts
     // for the possibility of __noSuchMethod__
@@ -302,28 +317,32 @@ Proxy::callProp(JSContext* cx, HandleObj
             return false;
     }
 #endif
 
     return true;
 }
 
 bool
-Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
+Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver_,
            ObjectOpResult& result)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
     if (!policy.allowed()) {
         if (!policy.returnValue())
             return false;
         return result.succeed();
     }
 
+    // Outerize the receiver. Proxy handlers shouldn't have to know about
+    // the Window/WindowProxy distinction.
+    RootedValue receiver(cx, OuterizeValue(cx, receiver_));
+
     // Special case. See the comment on BaseProxyHandler::mHasPrototype.
     if (handler->hasPrototype())
         return handler->BaseProxyHandler::set(cx, proxy, id, v, receiver, result);
 
     return handler->set(cx, proxy, id, v, receiver, result);
 }
 
 bool
@@ -338,43 +357,45 @@ Proxy::getOwnEnumerablePropertyKeys(JSCo
 }
 
 bool
 Proxy::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     objp.set(nullptr); // default result if we refuse to perform this action
-    if (!handler->hasPrototype()) {
-        AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
-                               BaseProxyHandler::ENUMERATE, true);
-        // If the policy denies access but wants us to return true, we need
-        // to hand a valid (empty) iterator object to the caller.
-        if (!policy.allowed()) {
-            return policy.returnValue() &&
-                   NewEmptyPropertyIterator(cx, 0, objp);
-        }
-        return handler->enumerate(cx, proxy, objp);
+
+    if (handler->hasPrototype()) {
+        AutoIdVector props(cx);
+        if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props))
+            return false;
+
+        RootedObject proto(cx);
+        if (!GetPrototype(cx, proxy, &proto))
+            return false;
+        if (!proto)
+            return EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp);
+        assertSameCompartment(cx, proxy, proto);
+
+        AutoIdVector protoProps(cx);
+        return GetPropertyKeys(cx, proto, 0, &protoProps) &&
+               AppendUnique(cx, props, protoProps) &&
+               EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp);
     }
 
-    AutoIdVector props(cx);
-    if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props))
-        return false;
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
+                           BaseProxyHandler::ENUMERATE, true);
 
-    RootedObject proto(cx);
-    if (!GetPrototype(cx, proxy, &proto))
-        return false;
-    if (!proto)
-        return EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp);
-    assertSameCompartment(cx, proxy, proto);
-
-    AutoIdVector protoProps(cx);
-    return GetPropertyKeys(cx, proto, 0, &protoProps) &&
-           AppendUnique(cx, props, protoProps) &&
-           EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp);
+    // If the policy denies access but wants us to return true, we need
+    // to hand a valid (empty) iterator object to the caller.
+    if (!policy.allowed()) {
+        return policy.returnValue() &&
+            NewEmptyPropertyIterator(cx, 0, objp);
+    }
+    return handler->enumerate(cx, proxy, objp);
 }
 
 bool
 Proxy::call(JSContext* cx, HandleObject proxy, const CallArgs& args)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/toLocaleString.js
@@ -0,0 +1,16 @@
+"use strict";
+
+Object.defineProperty(String.prototype, "toLocaleString", {
+    get() {
+        // Congratulations! You probably fixed primitive-this getters.
+        // Change "object" to "string".
+        assertEq(typeof this, "object");
+
+        return function() { return typeof this; };
+    }
+})
+
+assertEq(["test"].toLocaleString(), "string");
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Object/toLocaleString.js
@@ -0,0 +1,15 @@
+"use strict";
+
+Object.defineProperty(String.prototype, "toString", {
+    get() {
+        // Congratulations! You probably fixed primitive-this getters.
+        // Change "object" to "string".
+        assertEq(typeof this, "object");
+
+        return function() { return typeof this; };
+    }
+})
+assertEq(Object.prototype.toLocaleString.call("test"), "string");
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Proxy/global-receiver.js
@@ -0,0 +1,29 @@
+// The global object can be the receiver passed to the get and set traps of a Proxy.
+
+var global = this;
+var proto = Object.getPrototypeOf(global);
+var gets = 0, sets = 0;
+Object.setPrototypeOf(global, new Proxy(proto, {
+    has(t, id) {
+        return id === "bareword" || Reflect.has(t, id);
+    },
+    get(t, id, r) {
+        gets++;
+        assertEq(r, global);
+        return Reflect.get(t, id, r);
+    },
+    set(t, id, v, r) {
+        sets++;
+        assertEq(r, global);
+        return Reflect.set(t, id, v, r);
+    }
+}));
+
+assertEq(bareword, undefined);
+assertEq(gets, 1);
+
+bareword = 12;
+assertEq(sets, 1);
+assertEq(global.bareword, 12);
+
+reportCompare(0, 0);
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -2050,43 +2050,25 @@ NativeSetExistingDataProperty(JSContext*
  * or finds a writable data property on the prototype chain, we end up here.
  * Finish the [[Set]] by defining a new property on receiver.
  *
  * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.b-f, but it
  * is really old code and there are a few barnacles.
  */
 bool
 js::SetPropertyByDefining(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
-                          HandleValue receiverValue, bool objHasOwn, ObjectOpResult& result)
+                          HandleValue receiverValue, ObjectOpResult& result)
 {
     // Step 5.b.
     if (!receiverValue.isObject())
         return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
     RootedObject receiver(cx, &receiverValue.toObject());
 
     bool existing;
-    if (receiver == obj) {
-        // Steps 5.c-e.ii.
-        // The common case. The caller has necessarily done a property lookup
-        // on obj and passed us the answer as objHasOwn.
-        // We also know that the property is a data property and writable
-        // if it exists.
-#ifdef DEBUG
-        // Check that objHasOwn is correct. This could fail if receiver or a
-        // native object on its prototype chain has a nondeterministic resolve
-        // hook. We shouldn't have any that are quite that badly behaved.
-        Rooted<PropertyDescriptor> desc(cx);
-        if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc))
-            return false;
-        MOZ_ASSERT(!!desc.object() == objHasOwn);
-        MOZ_ASSERT_IF(desc.object(), desc.isDataDescriptor());
-        MOZ_ASSERT_IF(desc.object(), desc.writable());
-#endif
-        existing = objHasOwn;
-    } else {
+    {
         // Steps 5.c-d.
         Rooted<PropertyDescriptor> desc(cx);
         if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc))
             return false;
 
         existing = !!desc.object();
 
         // Step 5.e.
@@ -2151,17 +2133,17 @@ bool
 js::SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                        HandleValue receiver, ObjectOpResult& result)
 {
     MOZ_ASSERT(!obj->is<ProxyObject>());
 
     RootedObject proto(cx, obj->getProto());
     if (proto)
         return SetProperty(cx, proto, id, v, receiver, result);
-    return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
+    return SetPropertyByDefining(cx, obj, id, v, receiver, result);
 }
 
 /*
  * Implement "the rest of" assignment to a property when no property receiver[id]
  * was found anywhere on the prototype chain.
  *
  * FIXME: This should be updated to follow ES6 draft rev 28, section 9.1.9,
  * steps 4.d.i and 5.
@@ -2173,17 +2155,17 @@ SetNonexistentProperty(JSContext* cx, Ha
     // We should never add properties to lexical blocks.
     MOZ_ASSERT_IF(receiver.isObject(), !receiver.toObject().is<BlockObject>());
 
     if (!qualified && receiver.isObject() && receiver.toObject().isUnqualifiedVarObj()) {
         if (!MaybeReportUndeclaredVarAssignment(cx, JSID_TO_STRING(id)))
             return false;
     }
 
-    return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
+    return SetPropertyByDefining(cx, obj, id, v, receiver, result);
 }
 
 /*
  * Set an existing own property obj[index] that's a dense element or typed
  * array element.
  */
 static bool
 SetDenseOrTypedArrayElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v,
@@ -2234,17 +2216,17 @@ SetExistingProperty(JSContext* cx, Handl
     if (IsImplicitDenseOrTypedArrayElement(shape)) {
         // Step 5.a is a no-op: all dense elements are writable.
 
         // Pure optimization for the common case:
         if (receiver.isObject() && pobj == &receiver.toObject())
             return SetDenseOrTypedArrayElement(cx, pobj, JSID_TO_INT(id), v, result);
 
         // Steps 5.b-f.
-        return SetPropertyByDefining(cx, obj, id, v, receiver, obj == pobj, result);
+        return SetPropertyByDefining(cx, obj, id, v, receiver, result);
     }
 
     // Step 5 for all other properties.
     if (shape->isDataDescriptor()) {
         // Step 5.a.
         if (!shape->writable())
             return result.fail(JSMSG_READ_ONLY);
 
@@ -2272,17 +2254,17 @@ SetExistingProperty(JSContext* cx, Handl
                 return result.succeed();
 
             RootedValue valCopy(cx, v);
             return CallJSSetterOp(cx, shape->setterOp(), obj, id, &valCopy, result);
         }
 
         // Shadow pobj[id] by defining a new data property receiver[id].
         // Delegate everything to SetPropertyByDefining.
-        return SetPropertyByDefining(cx, obj, id, v, receiver, obj == pobj, result);
+        return SetPropertyByDefining(cx, obj, id, v, receiver, result);
     }
 
     // Steps 6-11.
     MOZ_ASSERT(shape->isAccessorDescriptor());
     MOZ_ASSERT_IF(!shape->hasSetterObject(), shape->hasDefaultSetter());
     if (shape->hasDefaultSetter())
         return result.fail(JSMSG_GETTER_ONLY);
     Value setter = ObjectValue(*shape->setterObject());
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -1307,17 +1307,17 @@ NativeGetProperty(JSContext* cx, HandleN
 inline bool
 NativeGetElement(JSContext* cx, HandleNativeObject obj, uint32_t index, MutableHandleValue vp)
 {
     return NativeGetElement(cx, obj, obj, index, vp);
 }
 
 bool
 SetPropertyByDefining(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
-                      HandleValue receiver, bool objHasOwn, ObjectOpResult& result);
+                      HandleValue receiver, ObjectOpResult& result);
 
 bool
 SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                    HandleValue receiver, ObjectOpResult& result);
 
 /*
  * Indicates whether an assignment operation is qualified (`x.y = 0`) or
  * unqualified (`y = 0`). In strict mode, the latter is an error if no such
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -813,17 +813,17 @@ UnboxedPlainObject::obj_setProperty(JSCo
             if (obj->as<UnboxedPlainObject>().setValue(cx, *property, v))
                 return result.succeed();
 
             if (!convertToNative(cx, obj))
                 return false;
             return SetProperty(cx, obj, id, v, receiver, result);
         }
 
-        return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
+        return SetPropertyByDefining(cx, obj, id, v, receiver, result);
     }
 
     if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
         if (expando->containsShapeOrElement(cx, id)) {
             // Update property types on the unboxed object as well.
             AddTypePropertyId(cx, obj, id, v);
 
             RootedObject nexpando(cx, expando);
@@ -1480,17 +1480,17 @@ UnboxedArrayObject::obj_setProperty(JSCo
                 return result.succeed();
             }
 
             if (!convertToNative(cx, obj))
                 return false;
             return SetProperty(cx, obj, id, v, receiver, result);
         }
 
-        return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
+        return SetPropertyByDefining(cx, obj, id, v, receiver, result);
     }
 
     return SetPropertyOnProto(cx, obj, id, v, receiver, result);
 }
 
 /* static */ bool
 UnboxedArrayObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
                                                  MutableHandle<JSPropertyDescriptor> desc)
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1146101-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script>
+function boom()
+{
+    document.getElementsByTagName("tbody")[0].style.position = "absolute";
+    document.getElementsByTagName("table")[0].style.color = "green";
+}
+</script>
+<body onload="boom();">
+<table><tbody></tbody></table>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -107,16 +107,17 @@ pref(layers.offmainthreadcomposition.asy
 load 989965-1.html
 load 992333-1.html
 pref(dom.webcomponents.enabled,true) load 1017798-1.html
 load 1028514-1.html
 load 1066089-1.html
 load 1074651-1.html
 pref(dom.webcomponents.enabled,true) load 1089463-1.html
 pref(layout.css.expensive-style-struct-assertions.enabled,true) load 1136010-1.html
+pref(layout.css.expensive-style-struct-assertions.enabled,true) load 1146101-1.html
 load 1153693-1.html
 load 1161320-1.html
 pref(dom.animations-api.core.enabled,true) load 1161320-2.html
 load 1161366-1.html
 load 1163446-1.html
 load 1164813-1.html
 load 1167782-1.html
 load large_border_image_width.html
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -928,16 +928,20 @@ nsJARChannel::OverrideWithSynthesizedRes
 
     rv = mSynthesizedResponsePump->AsyncRead(this, nullptr);
     NS_ENSURE_SUCCESS_VOID(rv);
 }
 
 NS_IMETHODIMP
 nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
 {
+    MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+               mLoadInfo->GetEnforceSecurity(),
+               "security flags in loadInfo but asyncOpen2() not called");
+
     LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this));
 
     NS_ENSURE_ARG_POINTER(listener);
     NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS);
     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
 
     mJarFile = nullptr;
     mIsUnsafe = true;
--- a/netwerk/base/nsBaseChannel.cpp
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -621,16 +621,20 @@ nsBaseChannel::Open2(nsIInputStream** aS
   nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
   NS_ENSURE_SUCCESS(rv, rv);
   return Open(aStream);
 }
 
 NS_IMETHODIMP
 nsBaseChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
 {
+  MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+             mLoadInfo->GetInitialSecurityCheckDone(),
+             "security flags in loadInfo but asyncOpen2() not called");
+
   NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
   NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS);
   NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
   NS_ENSURE_ARG(listener);
 
   // Ensure that this is an allowed port before proceeding.
   nsresult rv = NS_CheckPortSafety(mURI);
   if (NS_FAILED(rv)) {
--- a/netwerk/base/nsBaseChannel.h
+++ b/netwerk/base/nsBaseChannel.h
@@ -264,32 +264,32 @@ private:
     nsCOMPtr<nsIChannel> mNewChannel;
   };
   friend class RedirectRunnable;
 
   nsRefPtr<nsInputStreamPump>         mPump;
   nsCOMPtr<nsIProgressEventSink>      mProgressSink;
   nsCOMPtr<nsIURI>                    mOriginalURI;
   nsCOMPtr<nsISupports>               mOwner;
-  nsCOMPtr<nsILoadInfo>               mLoadInfo;
   nsCOMPtr<nsISupports>               mSecurityInfo;
   nsCOMPtr<nsIChannel>                mRedirectChannel;
   nsCString                           mContentType;
   nsCString                           mContentCharset;
   uint32_t                            mLoadFlags;
   bool                                mQueriedProgressSink;
   bool                                mSynthProgressEvents;
   bool                                mAllowThreadRetargeting;
   bool                                mWaitingOnAsyncRedirect;
   bool                                mOpenRedirectChannel;
   uint32_t                            mRedirectFlags;
 
 protected:
   nsCOMPtr<nsIURI>                    mURI;
   nsCOMPtr<nsILoadGroup>              mLoadGroup;
+  nsCOMPtr<nsILoadInfo>               mLoadInfo;
   nsCOMPtr<nsIInterfaceRequestor>     mCallbacks;
   nsCOMPtr<nsIStreamListener>         mListener;
   nsCOMPtr<nsISupports>               mListenerContext;
   nsresult                            mStatus;
   uint32_t                            mContentDispositionHint;
   nsAutoPtr<nsString>                 mContentDispositionFilename;
   int64_t                             mContentLength;
   bool                                mWasOpened;
--- a/netwerk/protocol/app/AppProtocolHandler.cpp
+++ b/netwerk/protocol/app/AppProtocolHandler.cpp
@@ -106,16 +106,20 @@ DummyChannel::Open2(nsIInputStream** aSt
   nsCOMPtr<nsIStreamListener> listener;
   nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
   NS_ENSURE_SUCCESS(rv, rv);
   return Open(aStream);
 }
 
 NS_IMETHODIMP DummyChannel::AsyncOpen(nsIStreamListener* aListener, nsISupports* aContext)
 {
+  MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+             mLoadInfo->GetInitialSecurityCheckDone(),
+             "security flags in loadInfo but asyncOpen2() not called");
+
   mListener = aListener;
   mListenerContext = aContext;
   mPending = true;
 
   if (mLoadGroup) {
     mLoadGroup->AddRequest(this, aContext);
   }
 
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -1483,16 +1483,20 @@ HttpChannelChild::GetSecurityInfo(nsISup
   NS_ENSURE_ARG_POINTER(aSecurityInfo);
   NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
 {
+  MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+             mLoadInfo->GetInitialSecurityCheckDone(),
+             "security flags in loadInfo but asyncOpen2() not called");
+
   LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get()));
 
   if (mCanceled)
     return mStatus;
 
   NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
   NS_ENSURE_ARG_POINTER(listener);
   NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -4948,16 +4948,20 @@ nsHttpChannel::GetSecurityInfo(nsISuppor
 // If any of the functions that AsyncOpen calls returns immediately an error
 // AsyncAbort(which calls onStart/onStopRequest) does not need to be call.
 // To be sure that they are not call ReleaseListeners() is called.
 // If AsyncOpen returns NS_OK, after that point AsyncAbort must be called on
 // any error.
 NS_IMETHODIMP
 nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context)
 {
+    MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+               mLoadInfo->GetInitialSecurityCheckDone(),
+               "security flags in loadInfo but asyncOpen2() not called");
+
     LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
 
     NS_ENSURE_ARG_POINTER(listener);
     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
     NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
 
     nsresult rv;
 
--- a/netwerk/protocol/rtsp/RtspChannelChild.cpp
+++ b/netwerk/protocol/rtsp/RtspChannelChild.cpp
@@ -102,16 +102,20 @@ private:
   nsRefPtr<nsIStreamListener> mListener;
   nsRefPtr<nsIRequest> mRequest;
   nsRefPtr<nsISupports> mContext;
 };
 
 NS_IMETHODIMP
 RtspChannelChild::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
 {
+  MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+             mLoadInfo->GetInitialSecurityCheckDone(),
+             "security flags in loadInfo but asyncOpen2() not called");
+
   // Precondition checks.
   MOZ_ASSERT(aListener);
   nsCOMPtr<nsIURI> uri = nsBaseChannel::URI();
   NS_ENSURE_TRUE(uri, NS_ERROR_ILLEGAL_VALUE);
 
   // Create RtspController.
   nsCOMPtr<nsIStreamingProtocolControllerService> mediaControllerService =
     do_GetService(MEDIASTREAMCONTROLLERSERVICE_CONTRACTID);
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -253,16 +253,25 @@ nsViewSourceChannel::Open2(nsIInputStrea
     // the necessary security checks.
     loadInfo->SetEnforceSecurity(true);
     return Open(aStream);
 }
 
 NS_IMETHODIMP
 nsViewSourceChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *ctxt)
 {
+#ifdef DEBUG
+    {
+    nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+    MOZ_ASSERT(!loadInfo || loadInfo->GetSecurityMode() == 0 ||
+               loadInfo->GetEnforceSecurity(),
+               "security flags in loadInfo but asyncOpen2() not called");
+    }
+#endif
+
     NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
 
     mListener = aListener;
 
     /*
      * We want to add ourselves to the loadgroup before opening
      * mChannel, since we want to make sure we're in the loadgroup
      * when mChannel finishes and fires OnStopRequest()
--- a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
@@ -621,16 +621,20 @@ GetTabChild(nsIChannel* aChannel)
   nsCOMPtr<nsITabChild> iTabChild;
   NS_QueryNotificationCallbacks(aChannel, iTabChild);
   return iTabChild ? static_cast<mozilla::dom::TabChild*>(iTabChild.get()) : nullptr;
 }
 
 NS_IMETHODIMP
 WyciwygChannelChild::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
 {
+  MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+             mLoadInfo->GetInitialSecurityCheckDone(),
+             "security flags in loadInfo but asyncOpen2() not called");
+
   LOG(("WyciwygChannelChild::AsyncOpen [this=%p]\n", this));
 
   // The only places creating wyciwyg: channels should be
   // HTMLDocument::OpenCommon and session history.  Both should be setting an
   // owner or loadinfo.
   NS_PRECONDITION(mOwner || mLoadInfo, "Must have a principal");
   NS_ENSURE_STATE(mOwner || mLoadInfo);
 
--- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
@@ -426,16 +426,20 @@ nsWyciwygChannel::Open2(nsIInputStream**
   nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
   NS_ENSURE_SUCCESS(rv, rv);
   return Open(aStream);
 }
 
 NS_IMETHODIMP
 nsWyciwygChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
 {
+  MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+             mLoadInfo->GetInitialSecurityCheckDone(),
+             "security flags in loadInfo but asyncOpen2() not called");
+
   LOG(("nsWyciwygChannel::AsyncOpen [this=%p]\n", this));
   MOZ_ASSERT(mMode == NONE, "nsWyciwygChannel already open");
 
   NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
   NS_ENSURE_TRUE(mMode == NONE, NS_ERROR_IN_PROGRESS);
   NS_ENSURE_ARG_POINTER(listener);
 
   mMode = READING;
--- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp
+++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp
@@ -258,28 +258,34 @@ nsSecureBrowserUIImpl::MapInternalToExte
       !docShell->GetHasMixedDisplayContentLoaded() &&
       !docShell->GetHasMixedActiveContentBlocked() &&
       !docShell->GetHasMixedDisplayContentBlocked()) {
     *aState = STATE_IS_SECURE;
     if (ev) {
       *aState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL;
     }
   }
-  // * If so, the state should be broken; overriding the previous state
-  // set by the lock parameter.
+  // * If so, the state should be broken or insecure; overriding the previous
+  // state set by the lock parameter.
+  uint32_t tempState = STATE_IS_BROKEN;
+  if (lock == lis_no_security) {
+      // this is to ensure that http: pages with mixed content in nested
+      // iframes don't get marked as broken instead of insecure
+      tempState = STATE_IS_INSECURE;
+  }
   if (docShell->GetHasMixedActiveContentLoaded() &&
       docShell->GetHasMixedDisplayContentLoaded()) {
-      *aState = STATE_IS_BROKEN |
+      *aState = tempState |
                 nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
                 nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
   } else if (docShell->GetHasMixedActiveContentLoaded()) {
-      *aState = STATE_IS_BROKEN |
+      *aState = tempState |
                 nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
   } else if (docShell->GetHasMixedDisplayContentLoaded()) {
-      *aState = STATE_IS_BROKEN |
+      *aState = tempState |
                 nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
   }
 
   // Has Mixed Content Been Blocked in nsMixedContentBlocker?
   if (docShell->GetHasMixedActiveContentBlocked())
     *aState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
 
   if (docShell->GetHasMixedDisplayContentBlocked())
--- a/testing/config/mozharness/b2g_emulator_config.py
+++ b/testing/config/mozharness/b2g_emulator_config.py
@@ -7,17 +7,16 @@ config = {
         "cppunittest": {
             "options": [
                 "--dm_trans=adb",
                 "--symbols-path=%(symbols_path)s",
                 "--xre-path=%(xre_path)s",
                 "--addEnv",
                 "LD_LIBRARY_PATH=/vendor/lib:/system/lib:/system/b2g",
                 "--with-b2g-emulator=%(b2gpath)s",
-                "--skip-manifest=b2g_cppunittest_manifest.txt",
                 "."
             ],
             "run_filename": "remotecppunittests.py",
             "testsdir": "cppunittest"
         },
         "crashtest": {
             "options": [
                 "--adbpath=%(adbpath)s",
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -294,19 +294,20 @@ class MachCommands(MachCommandBase):
         import runcppunittests as cppunittests
 
         log = commandline.setup_logging("cppunittest",
                                         {},
                                         {"tbpl": sys.stdout})
 
         if len(params['test_files']) == 0:
             testdir = os.path.join(self.distdir, 'cppunittests')
-            tests = cppunittests.extract_unittests_from_args([testdir], mozinfo.info)
+            manifest = os.path.join(self.topsrcdir, 'testing', 'cppunittest.ini')
+            tests = cppunittests.extract_unittests_from_args([testdir], mozinfo.info, manifest)
         else:
-            tests = cppunittests.extract_unittests_from_args(params['test_files'], mozinfo.info)
+            tests = cppunittests.extract_unittests_from_args(params['test_files'], mozinfo.info, None)
 
         # See if we have crash symbols
         symbols_path = os.path.join(self.distdir, 'crashreporter-symbols')
         if not os.path.isdir(symbols_path):
             symbols_path = None
 
         tester = cppunittests.CPPUnitTests()
         try:
--- a/testing/mozharness/scripts/spidermonkey_build.py
+++ b/testing/mozharness/scripts/spidermonkey_build.py
@@ -344,16 +344,24 @@ class SpidermonkeyBuild(MockMixin,
             self,
             always_clobber_dirs=[
                 dirs['abs_upload_dir'],
             ],
         )
 
     def checkout_tools(self):
         dirs = self.query_abs_dirs()
+
+        # If running from within a directory also passed as the --source dir,
+        # this has the danger of clobbering <source>/tools/
+        if self.config['source']:
+            srcdir = self.config['source']
+            if os.path.samefile(srcdir, os.path.dirname(dirs['abs_tools_dir'])):
+                raise Exception("Cannot run from source checkout to avoid overwriting subdirs")
+
         rev = self.vcs_checkout(
             vcs='hg',  # Don't have hgtool.py yet
             repo=self.config['tools_repo'],
             clean=False,
             dest=dirs['abs_tools_dir'],
         )
         self.set_buildbot_property("tools_revision", rev, write_to_file=True)
 
--- a/testing/remotecppunittests.py
+++ b/testing/remotecppunittests.py
@@ -109,34 +109,37 @@ class RemoteCPPUnitTests(cppunittests.CP
                     env[envdef_parts[0]] = envdef_parts[1]
                 elif len(envdef_parts) == 1:
                     env[envdef_parts[0]] = ""
                 else:
                     self.log.warning("invalid --addEnv option skipped: %s" % envdef)
 
         return env
 
-    def run_one_test(self, prog, env, symbols_path=None, interactive=False):
+    def run_one_test(self, prog, env, symbols_path=None, interactive=False,
+                     timeout_factor=1):
         """
         Run a single C++ unit test program remotely.
 
         Arguments:
         * prog: The path to the test program to run.
         * env: The environment to use for running the program.
         * symbols_path: A path to a directory containing Breakpad-formatted
                         symbol files for producing stack traces on crash.
+        * timeout_factor: An optional test-specific timeout multiplier.
 
         Return True if the program exits with a zero status, False otherwise.
         """
         basename = os.path.basename(prog)
         remote_bin = posixpath.join(self.remote_bin_dir, basename)
         self.log.test_start(basename)
         buf = StringIO.StringIO()
+        test_timeout = cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT * timeout_factor
         returncode = self.device.shell([remote_bin], buf, env=env, cwd=self.remote_home_dir,
-                                       timeout=cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT)
+                                       timeout=test_timeout)
         self.log.process_output(basename, "\n%s" % buf.getvalue(),
                                 command=[remote_bin])
         with mozfile.TemporaryDirectory() as tempdir:
             self.device.getDirectory(self.remote_home_dir, tempdir)
             if mozcrash.check_for_crashes(tempdir, symbols_path,
                                           test_name=basename):
                 self.log.test_end(basename, status='CRASH', expected='PASS')
                 return False
@@ -249,18 +252,20 @@ def main():
             print "Error: you must provide a device IP to connect to via the --deviceIP option"
             sys.exit(1)
 
     log = mozlog.commandline.setup_logging("remotecppunittests", options,
                                            {"tbpl": sys.stdout})
 
     options.xre_path = os.path.abspath(options.xre_path)
     cppunittests.update_mozinfo()
-    progs = cppunittests.extract_unittests_from_args(args, mozinfo.info)
-    tester = RemoteCPPUnitTests(dm, options, progs)
+    progs = cppunittests.extract_unittests_from_args(args,
+                                                     mozinfo.info,
+                                                     options.manifest_path)
+    tester = RemoteCPPUnitTests(dm, options, [item[0] for item in progs])
     try:
         result = tester.run_tests(progs, options.xre_path, options.symbols_path)
     except Exception, e:
         log.error(str(e))
         result = False
     if options.with_b2g_emulator:
         runner.cleanup()
         runner.wait()
--- a/testing/runcppunittests.py
+++ b/testing/runcppunittests.py
@@ -19,25 +19,27 @@ from subprocess import PIPE
 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
 
 class CPPUnitTests(object):
     # Time (seconds) to wait for test process to complete
     TEST_PROC_TIMEOUT = 900
     # Time (seconds) in which process will be killed if it produces no output.
     TEST_PROC_NO_OUTPUT_TIMEOUT = 300
 
-    def run_one_test(self, prog, env, symbols_path=None, interactive=False):
+    def run_one_test(self, prog, env, symbols_path=None, interactive=False,
+                     timeout_factor=1):
         """
         Run a single C++ unit test program.
 
         Arguments:
         * prog: The path to the test program to run.
         * env: The environment to use for running the program.
         * symbols_path: A path to a directory containing Breakpad-formatted
                         symbol files for producing stack traces on crash.
+        * timeout_factor: An optional test-specific timeout multiplier.
 
         Return True if the program exits with a zero status, False otherwise.
         """
         basename = os.path.basename(prog)
         self.log.test_start(basename)
         with mozfile.TemporaryDirectory() as tempdir:
             if interactive:
                 # For tests run locally, via mach, print output directly
@@ -48,17 +50,18 @@ class CPPUnitTests(object):
             else:
                 proc = mozprocess.ProcessHandler([prog],
                                                  cwd=tempdir,
                                                  env=env,
                                                  storeOutput=True,
                                                  processOutputLine=lambda _: None)
             #TODO: After bug 811320 is fixed, don't let .run() kill the process,
             # instead use a timeout in .wait() and then kill to get a stack.
-            proc.run(timeout=CPPUnitTests.TEST_PROC_TIMEOUT,
+            test_timeout = CPPUnitTests.TEST_PROC_TIMEOUT * timeout_factor
+            proc.run(timeout=test_timeout,
                      outputTimeout=CPPUnitTests.TEST_PROC_NO_OUTPUT_TIMEOUT)
             proc.wait()
             if proc.output:
                 output = "\n%s" % "\n".join(proc.output)
                 self.log.process_output(proc.pid, output, command=[prog])
             if proc.timedOut:
                 message = "timed out after %d seconds" % CPPUnitTests.TEST_PROC_TIMEOUT
                 self.log.test_end(basename, status='TIMEOUT', expected='PASS',
@@ -128,32 +131,35 @@ class CPPUnitTests(object):
 
         return env
 
     def run_tests(self, programs, xre_path, symbols_path=None, interactive=False):
         """
         Run a set of C++ unit test programs.
 
         Arguments:
-        * programs: An iterable containing paths to test programs.
+        * programs: An iterable containing (test path, test timeout factor) tuples
         * xre_path: A path to a directory containing a XUL Runtime Environment.
         * symbols_path: A path to a directory containing Breakpad-formatted
                         symbol files for producing stack traces on crash.
 
         Returns True if all test programs exited with a zero status, False
         otherwise.
         """
         self.xre_path = xre_path
         self.log = mozlog.get_default_logger()
         self.log.suite_start(programs)
         env = self.build_environment()
         pass_count = 0
         fail_count = 0
         for prog in programs:
-            single_result = self.run_one_test(prog, env, symbols_path, interactive)
+            test_path = prog[0]
+            timeout_factor = prog[1]
+            single_result = self.run_one_test(test_path, env, symbols_path,
+                                              interactive, timeout_factor)
             if single_result:
                 pass_count += 1
             else:
                 fail_count += 1
         self.log.suite_end()
 
         # Mozharness-parseable summary formatting.
         self.log.info("Result summary:")
@@ -167,43 +173,51 @@ class CPPUnittestOptions(OptionParser):
         self.add_option("--xre-path",
                         action = "store", type = "string", dest = "xre_path",
                         default = None,
                         help = "absolute path to directory containing XRE (probably xulrunner)")
         self.add_option("--symbols-path",
                         action = "store", type = "string", dest = "symbols_path",
                         default = None,
                         help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
-        self.add_option("--skip-manifest",
-                        action = "store", type = "string", dest = "manifest_file",
+        self.add_option("--manifest-path",
+                        action = "store", type = "string", dest = "manifest_path",
                         default = None,
-                        help = "absolute path to a manifest file")
+                        help = "path to test manifest, if different from the path to test binaries")
 
-def extract_unittests_from_args(args, environ):
+def extract_unittests_from_args(args, environ, manifest_path):
     """Extract unittests from args, expanding directories as needed"""
     mp = manifestparser.TestManifest(strict=True)
     tests = []
-    for p in args:
-        if os.path.isdir(p):
-            try:
-                mp.read(os.path.join(p, 'cppunittest.ini'))
-            except IOError:
-                tests.extend([os.path.abspath(os.path.join(p, x)) for x in os.listdir(p)])
-        else:
-            tests.append(os.path.abspath(p))
+    binary_path = None
+
+    if manifest_path:
+        mp.read(manifest_path)
+        binary_path = os.path.abspath(args[0])
+    else:
+        for p in args:
+            if os.path.isdir(p):
+                try:
+                    mp.read(os.path.join(p, 'cppunittest.ini'))
+                except IOError:
+                    tests.extend([(os.path.abspath(os.path.join(p, x)), 1) for x in os.listdir(p)])
+            else:
+                tests.append((os.path.abspath(p), 1))
 
     # we skip the existence check here because not all tests are built
     # for all platforms (and it will fail on Windows anyway)
-    if mozinfo.isWin:
-        tests.extend([test['path'] + '.exe' for test in mp.active_tests(exists=False, disabled=False, **environ)])
+    active_tests = mp.active_tests(exists=False, disabled=False, **environ)
+    suffix = '.exe' if mozinfo.isWin else ''
+    if binary_path:
+        tests.extend([(os.path.join(binary_path, test['relpath'] + suffix), int(test.get('requesttimeoutfactor', 1))) for test in active_tests])
     else:
-        tests.extend([test['path'] for test in mp.active_tests(exists=False, disabled=False, **environ)])
+        tests.extend([(test['path'] + suffix, int(test.get('requesttimeoutfactor', 1))) for test in active_tests])
 
     # skip non-existing tests
-    tests = [test for test in tests if os.path.isfile(test)]
+    tests = [test for test in tests if os.path.isfile(test[0])]
 
     return tests
 
 def update_mozinfo():
     """walk up directories to find mozinfo.json update the info"""
     path = SCRIPT_DIR
     dirs = set()
     while path != os.path.expanduser('~'):
@@ -218,22 +232,25 @@ def main():
     mozlog.commandline.add_logging_group(parser)
     options, args = parser.parse_args()
     if not args:
         print >>sys.stderr, """Usage: %s <test binary> [<test binary>...]""" % sys.argv[0]
         sys.exit(1)
     if not options.xre_path:
         print >>sys.stderr, """Error: --xre-path is required"""
         sys.exit(1)
+    if options.manifest_path and len(args) > 1:
+        print >>sys.stderr, "Error: multiple arguments not supported with --test-manifest"
+        sys.exit(1)
 
     log = mozlog.commandline.setup_logging("cppunittests", options,
                                            {"tbpl": sys.stdout})
 
     update_mozinfo()
-    progs = extract_unittests_from_args(args, mozinfo.info)
+    progs = extract_unittests_from_args(args, mozinfo.info, options.manifest_path)
     options.xre_path = os.path.abspath(options.xre_path)
     if mozinfo.isMac:
         options.xre_path = os.path.join(os.path.dirname(options.xre_path), 'Resources')
     tester = CPPUnitTests()
 
     try:
         result = tester.run_tests(progs, options.xre_path, options.symbols_path)
     except Exception as e:
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-event-async-respond-with.https.html.ini
+++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-event-async-respond-with.https.html.ini
@@ -1,3 +1,5 @@
 [fetch-event-async-respond-with.https.html]
   type: testharness
-  expected: CRASH
+  expected: OK
+  [Calling respondWith asynchronously throws an exception]
+    expected: FAIL
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html.ini
+++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html.ini
@@ -1,3 +1,6 @@
 [fetch-event-respond-with-stops-propagation.https.html]
   type: testharness
-  expected: CRASH
+  expected: OK
+  [respondWith() invokes stopImmediatePropagation()]
+    expected: FAIL
+
--- a/toolkit/components/gfx/SanityTest.js
+++ b/toolkit/components/gfx/SanityTest.js
@@ -69,16 +69,26 @@ function reportSnapshotResult(val) {
   histogram.add(val);
 }
 
 function reportTestReason(val) {
   let histogram = Services.telemetry.getHistogramById("GRAPHICS_SANITY_TEST_REASON");
   histogram.add(val);
 }
 
+function reportSnapshotContents(canvas) {
+  try {
+    var data = canvas.toDataURL();
+    Cc['@mozilla.org/observer-service;1'].
+        getService(Ci.nsIObserverService).
+        notifyObservers(null, "graphics-sanity-test-failed", data);
+  } catch (e) {
+  }
+}
+
 function annotateCrashReport(value) {
   try {
     // "1" if we're annotating the crash report, "" to remove the annotation.
     var crashReporter = Cc['@mozilla.org/toolkit/crash-reporter;1'].
                           getService(Ci.nsICrashReporter);
     crashReporter.annotateCrashReport("GraphicsSanityTest", value ? "1" : "");
   } catch (e) {
   }
@@ -162,17 +172,19 @@ let listener = {
   runSanityTest: function() {
     this.canvas = this.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     this.canvas.setAttribute("width", PAGE_WIDTH);
     this.canvas.setAttribute("height", PAGE_HEIGHT);
     this.ctx = this.canvas.getContext("2d");
 
     // Perform the compositor backbuffer test, which currently we use for
     // actually deciding whether to enable hardware media decoding.
-    testCompositor(this.win, this.ctx);
+    if (!testCompositor(this.win, this.ctx)) {
+      reportSnapshotContents(this.canvas);
+    }
 
     this.endTest();
   },
 
   receiveMessage(message) {
     let data = message.data;
     switch (message.name) {
       case "gfxSanity:ContentLoaded":
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -156,16 +156,17 @@ const PREF_TELEMETRY_ENABLED = "toolkit.
 const PREF_UPDATE_ENABLED = "app.update.enabled";
 const PREF_UPDATE_AUTODOWNLOAD = "app.update.auto";
 const PREF_SEARCH_COHORT = "browser.search.cohort";
 
 const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed";
 const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified";
 const SEARCH_SERVICE_TOPIC = "browser-search-service";
 const COMPOSITOR_CREATED_TOPIC = "compositor:created";
+const SANITY_TEST_FAILED_TOPIC = "graphics-sanity-test-failed";
 
 /**
  * Get the current browser.
  * @return a string with the locale or null on failure.
  */
 function getBrowserLocale() {
   try {
     return Cc["@mozilla.org/chrome/chrome-registry;1"].
@@ -809,23 +810,25 @@ EnvironmentCache.prototype = {
     }
   },
 
   _addObservers: function () {
     // Watch the search engine change and service topics.
     Services.obs.addObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC, false);
     Services.obs.addObserver(this, SEARCH_SERVICE_TOPIC, false);
     Services.obs.addObserver(this, COMPOSITOR_CREATED_TOPIC, false);
+    Services.obs.addObserver(this, SANITY_TEST_FAILED_TOPIC, false);
   },
 
   _removeObservers: function () {
     // Remove the search engine change and service observers.
     Services.obs.removeObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC);
     Services.obs.removeObserver(this, SEARCH_SERVICE_TOPIC);
     Services.obs.removeObserver(this, COMPOSITOR_CREATED_TOPIC);
+    Services.obs.removeObserver(this, SANITY_TEST_FAILED_TOPIC);
   },
 
   observe: function (aSubject, aTopic, aData) {
     this._log.trace("observe - aTopic: " + aTopic + ", aData: " + aData);
     switch (aTopic) {
       case SEARCH_ENGINE_MODIFIED_TOPIC:
         if (aData != "engine-default" && aData != "engine-current") {
           return;
@@ -841,16 +844,19 @@ EnvironmentCache.prototype = {
         this._updateSearchEngine();
         break;
       case COMPOSITOR_CREATED_TOPIC:
         // Full graphics information is not available until we have created at
         // least one off-main-thread-composited window. Thus we wait for the
         // first compositor to be created and then query nsIGfxInfo again.
         this._onCompositorCreated();
         break;
+      case SANITY_TEST_FAILED_TOPIC:
+        this._onGraphicsSanityTestFailed(aData);
+        break;
     }
   },
 
   /**
    * Get the default search engine.
    * @return {String} Returns the search engine identifier, "NONE" if no default search
    *         engine is defined or "UNDEFINED" if no engine identifier or name can be found.
    */
@@ -920,16 +926,21 @@ EnvironmentCache.prototype = {
     try {
       let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
       gfxData.features = gfxInfo.getFeatures();
     } catch (e) {
       this._log.error("nsIGfxInfo.getFeatures() caught error", e);
     }
   },
 
+  _onGraphicsSanityTestFailed: function (aData) {
+    let gfxData = this._currentEnvironment.system.gfx;
+    gfxData.sanityTestSnapshot = aData;
+  },
+
   /**
    * Get the build data in object form.
    * @return Object containing the build data.
    */
   _getBuild: function () {
     let buildData = {
       applicationId: Services.appinfo.ID || null,
       applicationName: Services.appinfo.name || null,
--- a/toolkit/content/contentAreaUtils.js
+++ b/toolkit/content/contentAreaUtils.js
@@ -124,42 +124,43 @@ function saveImageURL(aURL, aFileName, a
   internalSave(aURL, null, aFileName, aContentDisp, aContentType,
                aShouldBypassCache, aFilePickerTitleKey, null, aReferrer,
                aDoc, aSkipPrompt, null);
 }
 
 // This is like saveDocument, but takes any browser/frame-like element
 // (nsIFrameLoaderOwner) and saves the current document inside it,
 // whether in-process or out-of-process.
-function saveBrowser(aBrowser, aSkipPrompt)
+function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID=0)
 {
   if (!aBrowser) {
     throw "Must have a browser when calling saveBrowser";
   }
   let persistable = aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner)
                     .frameLoader
                     .QueryInterface(Ci.nsIWebBrowserPersistable);
-  persistable.startPersistence({
+  let stack = Components.stack.caller;
+  persistable.startPersistence(aOuterWindowID, {
     onDocumentReady: function (document) {
       saveDocument(document, aSkipPrompt);
+    },
+    onError: function (status) {
+      throw new Components.Exception("saveBrowser failed asynchronously in startPersistence",
+                                     status, stack);
     }
-    // This interface also has an |onError| method which takes an
-    // nsresult, but in case of asynchronous failure there isn't
-    // really anything useful that can be done here.
   });
 }
 
 // Saves a document; aDocument can be an nsIWebBrowserPersistDocument
 // (see saveBrowser, above) or an nsIDOMDocument.
 //
 // aDocument can also be a CPOW for a remote nsIDOMDocument, in which
 // case "save as" modes that serialize the document's DOM are
 // unavailable.  This is a temporary measure for the "Save Frame As"
-// command (bug 1141337), and it's possible that there could be
-// add-ons doing something similar.
+// command (bug 1141337) and pre-e10s add-ons.
 function saveDocument(aDocument, aSkipPrompt)
 {
   const Ci = Components.interfaces;
 
   if (!aDocument)
     throw "Must have a document when calling saveDocument";
 
   let contentDisposition = null;
--- a/uriloader/exthandler/nsExternalProtocolHandler.cpp
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -181,16 +181,20 @@ NS_IMETHODIMP nsExtProtocolChannel::Open
   nsCOMPtr<nsIStreamListener> listener;
   nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
   NS_ENSURE_SUCCESS(rv, rv);
   return Open(aStream);
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
 {
+  MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
+             mLoadInfo->GetInitialSecurityCheckDone(),
+             "security flags in loadInfo but asyncOpen2() not called");
+
   NS_ENSURE_ARG_POINTER(listener);
   NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
 
   mWasOpened = true;
 
   return OpenURL();
 }
 
--- a/webapprt/content/webapp.js
+++ b/webapprt/content/webapp.js
@@ -15,21 +15,16 @@ XPCOMUtils.defineLazyGetter(this, "gAppB
                             function() document.getElementById("content"));
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
                                    "@mozilla.org/toolkit/crash-reporter;1",
                                    "nsICrashReporter");
 #endif
 
-function isSameOrigin(url) {
-  let origin = Services.io.newURI(url, null, null).prePath;
-  return (origin == WebappRT.config.app.origin);
-}
-
 let progressListener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference]),
   onLocationChange: function onLocationChange(progress, request, location,
                                               flags) {
 
     // Close tooltip (code adapted from /browser/base/content/browser.js)
     let pageTooltip = document.getElementById("contentAreaTooltip");
@@ -46,73 +41,84 @@ let progressListener = {
           if (tooltipWindow == progress.DOMWindow) {
             pageTooltip.hidePopup();
             break;
           }
         }
       }
     }
 
+    let isSameOrigin = (location.prePath === WebappRT.config.app.origin);
+
     // Set the title of the window to the name of the webapp, adding the origin
     // of the page being loaded if it's from a different origin than the app
     // (per security bug 741955, which specifies that other-origin pages loaded
     // in runtime windows must be identified in chrome).
     let title = WebappRT.localeManifest.name;
-    if (!isSameOrigin(location.spec)) {
+    if (!isSameOrigin) {
       title = location.prePath + " - " + title;
     }
     document.documentElement.setAttribute("title", title);
+
+#ifndef XP_WIN
+#ifndef XP_MACOSX
+    if (isSameOrigin) {
+      // On non-Windows platforms, we open new windows in fullscreen mode
+      // if the opener window is in fullscreen mode, so we hide the menubar;
+      // but on Mac we don't need to hide the menubar.
+      if (document.mozFullScreenElement) {
+        document.getElementById("main-menubar").style.display = "none";
+      }
+    }
+#endif
+#endif
   },
 
   onStateChange: function onStateChange(aProgress, aRequest, aFlags, aStatus) {
     if (aRequest instanceof Ci.nsIChannel &&
         aFlags & Ci.nsIWebProgressListener.STATE_START &&
         aFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT) {
       updateCrashReportURL(aRequest.URI);
     }
   }
 };
 
 function onOpenWindow(event) {
-  let name = event.detail.name;
+  if (event.detail.name === "_blank") {
+    let uri = Services.io.newURI(event.detail.url, null, null);
 
-  if (name == "_blank") {
-    let uri = Services.io.newURI(event.detail.url, null, null);
+    // Prevent the default handler so nsContentTreeOwner.ProvideWindow
+    // doesn't create the window itself.
+    event.preventDefault();
 
     // Direct the URL to the browser.
     Cc["@mozilla.org/uriloader/external-protocol-service;1"].
     getService(Ci.nsIExternalProtocolService).
     getProtocolHandlerInfo(uri.scheme).
     launchWithURI(uri);
-  } else {
-    let win = window.openDialog("chrome://webapprt/content/webapp.xul",
-                                name,
-                                "chrome,dialog=no,resizable," + event.detail.features);
+  }
 
-    win.addEventListener("load", function onLoad() {
-      win.removeEventListener("load", onLoad, false);
+  // Otherwise, don't do anything to make nsContentTreeOwner.ProvideWindow
+  // create the window itself and return it to the window.open caller.
+}
 
-#ifndef XP_WIN
-#ifndef XP_MACOSX
-      if (isSameOrigin(event.detail.url)) {
-        // On non-Windows platforms, we open new windows in fullscreen mode
-        // if the opener window is in fullscreen mode, so we hide the menubar;
-        // but on Mac we don't need to hide the menubar.
-        if (document.mozFullScreenElement) {
-          win.document.getElementById("main-menubar").style.display = "none";
-        }
-      }
-#endif
-#endif
-
-      win.document.getElementById("content").docShell.setIsApp(WebappRT.appID);
-      win.document.getElementById("content").setAttribute("src", event.detail.url);
-    }, false);
+function onDOMContentLoaded() {
+  window.removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
+  // The initial window's app ID is set by Startup.jsm before the app
+  // is loaded, so this code only handles subsequent windows that are opened
+  // by the app via window.open calls.  We do this on DOMContentLoaded
+  // in order to ensure it gets set before the window's content is loaded.
+  if (gAppBrowser.docShell.appId === Ci.nsIScriptSecurityManager.NO_APP_ID) {
+    // Set the principal to the correct app ID.  Since this is a subsequent
+    // window, we know that WebappRT.configPromise has been resolved, so we
+    // don't have to yield to it before accessing WebappRT.appID.
+    gAppBrowser.docShell.setIsApp(WebappRT.appID);
   }
 }
+window.addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
 
 function onLoad() {
   window.removeEventListener("load", onLoad, false);
 
   gAppBrowser.addProgressListener(progressListener,
                                   Ci.nsIWebProgress.NOTIFY_LOCATION |
                                   Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
 
--- a/webapprt/test/chrome/browser_window-open.js
+++ b/webapprt/test/chrome/browser_window-open.js
@@ -22,17 +22,22 @@ function test() {
       win.addEventListener("load", function onLoadWindow() {
         win.removeEventListener("load", onLoadWindow, false);
 
         if (win.location == "chrome://webapprt/content/webapp.xul") {
           let winAppBrowser = win.document.getElementById("content");
           winAppBrowser.addEventListener("load", function onLoadBrowser() {
             winAppBrowser.removeEventListener("load", onLoadBrowser, true);
 
-            is(winAppBrowser.getAttribute("src"),
+            let contentWindow = Cu.waiveXrays(gAppBrowser.contentDocument.defaultView);
+            is(contentWindow.openedWindow.location.href,
+               "http://test/webapprtChrome/webapprt/test/chrome/sample.html",
+               "window.open returns window with correct URL");
+
+            is(winAppBrowser.documentURI.spec,
                "http://test/webapprtChrome/webapprt/test/chrome/sample.html",
                "New window browser has correct src");
 
             is(winAppBrowser.contentDocument.defaultView.document.nodePrincipal.appId,
                appID,
                "New window principal app ID correct");
 
             win.close();
--- a/webapprt/test/chrome/window-open.html
+++ b/webapprt/test/chrome/window-open.html
@@ -1,15 +1,16 @@
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>Window Open Test App</title>
     <meta charset="utf-8">
     <script>
+      var openedWindow;
       function onLoad() {
-        window.open("sample.html");
+        openedWindow = window.open("sample.html");
       }
     </script>
   </head>
   <body onload="onLoad()">
     <h1>Window Open Test App</h1>
   </body>
 </html>
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -19,17 +19,17 @@ public:
     typedef mozilla::jni::LocalRef<ANRReporter> LocalRef;
     typedef mozilla::jni::GlobalRef<ANRReporter> GlobalRef;
     typedef const mozilla::jni::Param<ANRReporter>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/ANRReporter";
 
 protected:
-    using Class::Class;
+    ANRReporter(jobject instance) : Class(instance) {}
 
 public:
     struct GetNativeStack_t {
         typedef ANRReporter Owner;
         typedef mozilla::jni::String::LocalRef ReturnType;
         typedef mozilla::jni::String::Param SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "getNativeStack";
@@ -83,17 +83,17 @@ public:
     typedef mozilla::jni::LocalRef<DownloadsIntegration> LocalRef;
     typedef mozilla::jni::GlobalRef<DownloadsIntegration> GlobalRef;
     typedef const mozilla::jni::Param<DownloadsIntegration>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/DownloadsIntegration";
 
 protected:
-    using Class::Class;
+    DownloadsIntegration(jobject instance) : Class(instance) {}
 
 public:
     struct ScanMedia_t {
         typedef DownloadsIntegration Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param,
@@ -118,17 +118,17 @@ public:
     typedef mozilla::jni::LocalRef<GeckoAppShell> LocalRef;
     typedef mozilla::jni::GlobalRef<GeckoAppShell> GlobalRef;
     typedef const mozilla::jni::Param<GeckoAppShell>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/GeckoAppShell";
 
 protected:
-    using Class::Class;
+    GeckoAppShell(jobject instance) : Class(instance) {}
 
 public:
     struct AcknowledgeEvent_t {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "acknowledgeEvent";
@@ -1650,17 +1650,17 @@ public:
     typedef mozilla::jni::LocalRef<GeckoJavaSampler> LocalRef;
     typedef mozilla::jni::GlobalRef<GeckoJavaSampler> GlobalRef;
     typedef const mozilla::jni::Param<GeckoJavaSampler>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/GeckoJavaSampler";
 
 protected:
-    using Class::Class;
+    GeckoJavaSampler(jobject instance) : Class(instance) {}
 
 public:
     struct GetFrameNameJavaProfilingWrapper_t {
         typedef GeckoJavaSampler Owner;
         typedef mozilla::jni::String::LocalRef ReturnType;
         typedef mozilla::jni::String::Param SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
@@ -1793,17 +1793,17 @@ public:
     typedef mozilla::jni::LocalRef<GeckoThread> LocalRef;
     typedef mozilla::jni::GlobalRef<GeckoThread> GlobalRef;
     typedef const mozilla::jni::Param<GeckoThread>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/GeckoThread";
 
 protected:
-    using Class::Class;
+    GeckoThread(jobject instance) : Class(instance) {}
 
 public:
     class State;
 
 public:
     struct PumpMessageLoop_t {
         typedef GeckoThread Owner;
         typedef bool ReturnType;
@@ -1884,17 +1884,17 @@ public:
     typedef mozilla::jni::LocalRef<State> LocalRef;
     typedef mozilla::jni::GlobalRef<State> GlobalRef;
     typedef const mozilla::jni::Param<State>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/GeckoThread$State";
 
 protected:
-    using Class::Class;
+    State(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef State Owner;
         typedef State::LocalRef ReturnType;
         typedef State::Param SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param,
@@ -2163,17 +2163,17 @@ public:
     typedef mozilla::jni::LocalRef<RestrictedProfiles> LocalRef;
     typedef mozilla::jni::GlobalRef<RestrictedProfiles> GlobalRef;
     typedef const mozilla::jni::Param<RestrictedProfiles>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/RestrictedProfiles";
 
 protected:
-    using Class::Class;
+    RestrictedProfiles(jobject instance) : Class(instance) {}
 
 public:
     struct IsAllowed_t {
         typedef RestrictedProfiles Owner;
         typedef bool ReturnType;
         typedef bool SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
@@ -2215,17 +2215,17 @@ public:
     typedef mozilla::jni::LocalRef<SurfaceBits> LocalRef;
     typedef mozilla::jni::GlobalRef<SurfaceBits> GlobalRef;
     typedef const mozilla::jni::Param<SurfaceBits>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/SurfaceBits";
 
 protected:
-    using Class::Class;
+    SurfaceBits(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef SurfaceBits Owner;
         typedef SurfaceBits::LocalRef ReturnType;
         typedef SurfaceBits::Param SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "<init>";
@@ -2324,17 +2324,17 @@ public:
     typedef mozilla::jni::LocalRef<ThumbnailHelper> LocalRef;
     typedef mozilla::jni::GlobalRef<ThumbnailHelper> GlobalRef;
     typedef const mozilla::jni::Param<ThumbnailHelper>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/ThumbnailHelper";
 
 protected:
-    using Class::Class;
+    ThumbnailHelper(jobject instance) : Class(instance) {}
 
 public:
     struct SendThumbnail_t {
         typedef ThumbnailHelper Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::Object::Param,
@@ -2361,17 +2361,17 @@ public:
     typedef mozilla::jni::LocalRef<DisplayPortMetrics> LocalRef;
     typedef mozilla::jni::GlobalRef<DisplayPortMetrics> GlobalRef;
     typedef const mozilla::jni::Param<DisplayPortMetrics>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/gfx/DisplayPortMetrics";
 
 protected:
-    using Class::Class;
+    DisplayPortMetrics(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef DisplayPortMetrics Owner;
         typedef DisplayPortMetrics::LocalRef ReturnType;
         typedef DisplayPortMetrics::Param SetterType;
         typedef mozilla::jni::Args<
                 float,
@@ -2433,17 +2433,17 @@ public:
     typedef mozilla::jni::LocalRef<GLController> LocalRef;
     typedef mozilla::jni::GlobalRef<GLController> GlobalRef;
     typedef const mozilla::jni::Param<GLController>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/gfx/GLController";
 
 protected:
-    using Class::Class;
+    GLController(jobject instance) : Class(instance) {}
 
 public:
     struct CreateEGLSurfaceForCompositorWrapper_t {
         typedef GLController Owner;
         typedef mozilla::jni::Object::LocalRef ReturnType;
         typedef mozilla::jni::Object::Param SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "createEGLSurfaceForCompositor";
@@ -2466,17 +2466,17 @@ public:
     typedef mozilla::jni::LocalRef<GeckoLayerClient> LocalRef;
     typedef mozilla::jni::GlobalRef<GeckoLayerClient> GlobalRef;
     typedef const mozilla::jni::Param<GeckoLayerClient>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/gfx/GeckoLayerClient";
 
 protected:
-    using Class::Class;
+    GeckoLayerClient(jobject instance) : Class(instance) {}
 
 public:
     struct ActivateProgram_t {
         typedef GeckoLayerClient Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "activateProgram";
@@ -2716,17 +2716,17 @@ public:
     typedef mozilla::jni::LocalRef<ImmutableViewportMetrics> LocalRef;
     typedef mozilla::jni::GlobalRef<ImmutableViewportMetrics> GlobalRef;
     typedef const mozilla::jni::Param<ImmutableViewportMetrics>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/gfx/ImmutableViewportMetrics";
 
 protected:
-    using Class::Class;
+    ImmutableViewportMetrics(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef ImmutableViewportMetrics Owner;
         typedef ImmutableViewportMetrics::LocalRef ReturnType;
         typedef ImmutableViewportMetrics::Param SetterType;
         typedef mozilla::jni::Args<
                 float,
@@ -2762,17 +2762,17 @@ public:
     typedef mozilla::jni::LocalRef<LayerView> LocalRef;
     typedef mozilla::jni::GlobalRef<LayerView> GlobalRef;
     typedef const mozilla::jni::Param<LayerView>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/gfx/LayerView";
 
 protected:
-    using Class::Class;
+    LayerView(jobject instance) : Class(instance) {}
 
 public:
     struct RegisterCompositorWrapper_t {
         typedef LayerView Owner;
         typedef mozilla::jni::Object::LocalRef ReturnType;
         typedef mozilla::jni::Object::Param SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "registerCxxCompositor";
@@ -2813,17 +2813,17 @@ public:
     typedef mozilla::jni::LocalRef<NativePanZoomController> LocalRef;
     typedef mozilla::jni::GlobalRef<NativePanZoomController> GlobalRef;
     typedef const mozilla::jni::Param<NativePanZoomController>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/gfx/NativePanZoomController";
 
 protected:
-    using Class::Class;
+    NativePanZoomController(jobject instance) : Class(instance) {}
 
 public:
     struct RequestContentRepaintWrapper_t {
         typedef NativePanZoomController Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 float,
@@ -2851,17 +2851,17 @@ public:
     typedef mozilla::jni::LocalRef<ProgressiveUpdateData> LocalRef;
     typedef mozilla::jni::GlobalRef<ProgressiveUpdateData> GlobalRef;
     typedef const mozilla::jni::Param<ProgressiveUpdateData>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/gfx/ProgressiveUpdateData";
 
 protected:
-    using Class::Class;
+    ProgressiveUpdateData(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef ProgressiveUpdateData Owner;
         typedef ProgressiveUpdateData::LocalRef ReturnType;
         typedef ProgressiveUpdateData::Param SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "<init>";
@@ -2978,17 +2978,17 @@ public:
     typedef mozilla::jni::LocalRef<ViewTransform> LocalRef;
     typedef mozilla::jni::GlobalRef<ViewTransform> GlobalRef;
     typedef const mozilla::jni::Param<ViewTransform>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/gfx/ViewTransform";
 
 protected:
-    using Class::Class;
+    ViewTransform(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef ViewTransform Owner;
         typedef ViewTransform::LocalRef ReturnType;
         typedef ViewTransform::Param SetterType;
         typedef mozilla::jni::Args<
                 float,
@@ -3185,17 +3185,17 @@ public:
     typedef mozilla::jni::LocalRef<MatrixBlobCursor> LocalRef;
     typedef mozilla::jni::GlobalRef<MatrixBlobCursor> GlobalRef;
     typedef const mozilla::jni::Param<MatrixBlobCursor>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/sqlite/MatrixBlobCursor";
 
 protected:
-    using Class::Class;
+    MatrixBlobCursor(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef MatrixBlobCursor Owner;
         typedef MatrixBlobCursor::LocalRef ReturnType;
         typedef MatrixBlobCursor::Param SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::ObjectArray::Param> Args;
@@ -3293,17 +3293,17 @@ public:
     typedef mozilla::jni::LocalRef<SQLiteBridgeException> LocalRef;
     typedef mozilla::jni::GlobalRef<SQLiteBridgeException> GlobalRef;
     typedef const mozilla::jni::Param<SQLiteBridgeException>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/sqlite/SQLiteBridgeException";
 
 protected:
-    using Class::Class;
+    SQLiteBridgeException(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef SQLiteBridgeException Owner;
         typedef SQLiteBridgeException::LocalRef ReturnType;
         typedef SQLiteBridgeException::Param SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "<init>";
@@ -3361,17 +3361,17 @@ public:
     typedef mozilla::jni::LocalRef<Clipboard> LocalRef;
     typedef mozilla::jni::GlobalRef<Clipboard> GlobalRef;
     typedef const mozilla::jni::Param<Clipboard>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/util/Clipboard";
 
 protected:
-    using Class::Class;
+    Clipboard(jobject instance) : Class(instance) {}
 
 public:
     struct ClearText_t {
         typedef Clipboard Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "clearText";
@@ -3446,17 +3446,17 @@ public:
     typedef mozilla::jni::LocalRef<NativeJSContainer> LocalRef;
     typedef mozilla::jni::GlobalRef<NativeJSContainer> GlobalRef;
     typedef const mozilla::jni::Param<NativeJSContainer>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/util/NativeJSContainer";
 
 protected:
-    using Class::Class;
+    NativeJSContainer(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef NativeJSContainer Owner;
         typedef NativeJSContainer::LocalRef ReturnType;
         typedef NativeJSContainer::Param SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "<init>";
@@ -3511,17 +3511,17 @@ public:
     typedef mozilla::jni::LocalRef<NativeJSObject> LocalRef;
     typedef mozilla::jni::GlobalRef<NativeJSObject> GlobalRef;
     typedef const mozilla::jni::Param<NativeJSObject>& Param;
 
     static constexpr char name[] =
             "org/mozilla/gecko/util/NativeJSObject";
 
 protected:
-    using Class::Class;
+    NativeJSObject(jobject instance) : Class(instance) {}
 
 public:
     struct New_t {
         typedef NativeJSObject Owner;
         typedef NativeJSObject::LocalRef ReturnType;
         typedef NativeJSObject::Param SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "<init>";
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -135,47 +135,47 @@ nsAppShell::nsAppShell()
       mQueuedViewportEvent(nullptr)
 {
     gAppShell = this;
 
     if (!XRE_IsParentProcess()) {
         return;
     }
 
+    if (jni::IsAvailable()) {
+        // Initialize JNI and Set the corresponding state in GeckoThread.
+        AndroidBridge::ConstructBridge();
+        mozilla::ANRReporter::Init();
+
+        widget::GeckoThread::SetState(widget::GeckoThread::State::JNI_READY());
+    }
+
     sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID);
 
     if (sPowerManagerService) {
         sWakeLockListener = new WakeLockListener();
     } else {
         NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
     }
-
-    // Initialize JNI and Set the corresponding state in GeckoThread.
-
-    if (!jni::IsAvailable()) {
-        return;
-    }
-    AndroidBridge::ConstructBridge();
-    mozilla::ANRReporter::Init();
-
-    widget::GeckoThread::SetState(widget::GeckoThread::State::JNI_READY());
 }
 
 nsAppShell::~nsAppShell()
 {
     gAppShell = nullptr;
 
     if (sPowerManagerService) {
         sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
 
         sPowerManagerService = nullptr;
         sWakeLockListener = nullptr;
     }
 
-    AndroidBridge::DeconstructBridge();
+    if (jni::IsAvailable()) {
+        AndroidBridge::DeconstructBridge();
+    }
 }
 
 void
 nsAppShell::NotifyNativeEvent()
 {
     MutexAutoLock lock(mCondLock);
     mQueueCond.Notify();
 }
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -2793,16 +2793,17 @@ RectTextureImage::UpdateFromCGContext(co
 void
 RectTextureImage::Draw(GLManager* aManager,
                        const nsIntPoint& aLocation,
                        const Matrix4x4& aTransform)
 {
   ShaderProgramOGL* program = aManager->GetProgram(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
                                                    gfx::SurfaceFormat::R8G8B8A8);
 
+  aManager->gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
   aManager->gl()->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, mTexture);
 
   aManager->ActivateProgram(program);
   program->SetProjectionMatrix(aManager->GetProjMatrix());
   program->SetLayerTransform(Matrix4x4(aTransform).PostTranslate(aLocation.x, aLocation.y, 0));
   program->SetTextureTransform(gfx::Matrix4x4());
   program->SetRenderOffset(nsIntPoint(0, 0));
   program->SetTexCoordMultiplier(mUsedSize.width, mUsedSize.height);
--- a/widget/windows/nsDataObj.cpp
+++ b/widget/windows/nsDataObj.cpp
@@ -69,24 +69,24 @@ nsresult nsDataObj::CStream::Init(nsIURI
   // we can not create a channel without a requestingNode
   if (!aRequestingNode) {
     return NS_ERROR_FAILURE;
   }
   nsresult rv;
   rv = NS_NewChannel(getter_AddRefs(mChannel),
                      pSourceURI,
                      aRequestingNode,
-                     nsILoadInfo::SEC_NORMAL,
+                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
                      nsIContentPolicy::TYPE_OTHER,
                      nullptr,   // loadGroup
                      nullptr,   // aCallbacks
                      nsIRequest::LOAD_FROM_CACHE);
 
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = mChannel->AsyncOpen(this, nullptr);
+  rv = mChannel->AsyncOpen2(this);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by
 // IUnknown and nsIStreamListener.
 STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid, void** ppvResult)
--- a/xpcom/ds/nsSupportsArray.cpp
+++ b/xpcom/ds/nsSupportsArray.cpp
@@ -184,16 +184,19 @@ NS_IMPL_ISUPPORTS(nsSupportsArray, nsISu
 
 NS_IMETHODIMP
 nsSupportsArray::Read(nsIObjectInputStream* aStream)
 {
   nsresult rv;
 
   uint32_t newArraySize;
   rv = aStream->Read32(&newArraySize);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   if (newArraySize <= kAutoArraySize) {
     if (mArray != mAutoArray) {
       delete[] mArray;
       mArray = mAutoArray;
     }
     newArraySize = kAutoArraySize;
   } else {