Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 16 Feb 2017 09:26:25 -0800
changeset 485445 4158b1d8bb2ab89048c8be560b35fcfc12726a84
parent 485407 25929185c46777cf4e9eb3e9aad2f8a8a227178f (current diff)
parent 485444 ab0a5b797f02b569bce706e7d6c08f99729687f9 (diff)
child 485446 bf6b9caab2c7eb3ebc642afd82bc19598829c6f3
push id45733
push useraklotz@mozilla.com
push dateThu, 16 Feb 2017 17:45:58 +0000
reviewersmerge
milestone54.0a1
Merge autoland to central, a=merge MozReview-Commit-ID: 64XN67WNMhM
--- a/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
@@ -131,30 +131,44 @@ function getPersistentStoragePermStatus(
 function getQuotaUsage(origin) {
   return new Promise(resolve => {
     let uri = NetUtil.newURI(origin);
     let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
     Services.qms.getUsageForPrincipal(principal, request => resolve(request.usage));
   });
 }
 
-function getCacheUsage() {
-  return new Promise(resolve => {
-    let obs = {
-      onNetworkCacheDiskConsumption(usage) {
-        resolve(usage);
-      },
-      QueryInterface: XPCOMUtils.generateQI([
-        Components.interfaces.nsICacheStorageConsumptionObserver,
-        Components.interfaces.nsISupportsWeakReference
-      ]),
-    };
-    Services.cache2.asyncGetDiskConsumption(obs);
-  });
-}
+// XXX: The intermittent bug 1331851
+// The implementation of nsICacheStorageConsumptionObserver must be passed as weak referenced,
+// so we must hold this observer here well. If we didn't, there would be a chance that
+// in Linux debug test run the observer was released before the operation at gecko was completed
+// (may be because of a relatively quicker GC cycle or a relatively slower operation).
+// As a result of that, we would never get the cache usage we want so the test would fail from timeout.
+const cacheUsageGetter = {
+  _promise: null,
+  _resolve: null,
+  get() {
+    if (!this._promise) {
+      this._promise = new Promise(resolve => {
+        this._resolve = resolve;
+        Services.cache2.asyncGetDiskConsumption(this);
+      });
+    }
+    return this._promise;
+  },
+  // nsICacheStorageConsumptionObserver implementations
+  onNetworkCacheDiskConsumption(usage) {
+    cacheUsageGetter._promise = null;
+    cacheUsageGetter._resolve(usage);
+  },
+  QueryInterface: XPCOMUtils.generateQI([
+    Components.interfaces.nsICacheStorageConsumptionObserver,
+    Components.interfaces.nsISupportsWeakReference
+  ]),
+};
 
 function openSettingsDialog() {
   let doc = gBrowser.selectedBrowser.contentDocument;
   let settingsBtn = doc.getElementById("siteDataSettings");
   let dialogOverlay = doc.getElementById("dialogOverlay");
   let dialogLoadPromise = promiseLoadSubDialog("chrome://browser/content/preferences/siteDataSettings.xul");
   let dialogInitPromise = TestUtils.topicObserved("sitedata-settings-init", () => true);
   let fullyLoadPromise = Promise.all([ dialogLoadPromise, dialogInitPromise ]).then(() => {
@@ -217,17 +231,17 @@ add_task(function* () {
 
   yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_BASE_URL + "site_data_test.html");
   yield waitForEvent(gBrowser.selectedBrowser.contentWindow, "test-indexedDB-done");
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
 
   // Test the initial states
-  let cacheUsage = yield getCacheUsage();
+  let cacheUsage = yield cacheUsageGetter.get();
   let quotaUsage = yield getQuotaUsage(TEST_ORIGIN);
   let totalUsage = yield SiteDataManager.getTotalUsage();
   Assert.greater(cacheUsage, 0, "The cache usage should not be 0");
   Assert.greater(quotaUsage, 0, "The quota usage should not be 0");
   Assert.greater(totalUsage, 0, "The total usage should not be 0");
 
   // Test cancelling "Clear All Data"
   // Click "Clear All Data" button and then cancel
@@ -236,17 +250,17 @@ add_task(function* () {
   let clearBtn = doc.getElementById("clearSiteDataButton");
   clearBtn.doCommand();
   yield cancelPromise;
 
   // Test the items are not removed
   let status = getPersistentStoragePermStatus(TEST_ORIGIN);
   is(status, Ci.nsIPermissionManager.ALLOW_ACTION, "Should not remove permission");
 
-  cacheUsage = yield getCacheUsage();
+  cacheUsage = yield cacheUsageGetter.get();
   quotaUsage = yield getQuotaUsage(TEST_ORIGIN);
   totalUsage = yield SiteDataManager.getTotalUsage();
   Assert.greater(cacheUsage, 0, "The cache usage should not be 0");
   Assert.greater(quotaUsage, 0, "The quota usage should not be 0");
   Assert.greater(totalUsage, 0, "The total usage should not be 0");
   // Test cancelling "Clear All Data" ends
 
   // Test accepting "Clear All Data"
@@ -264,17 +278,17 @@ add_task(function* () {
   // Test all the items are removed
   yield cookiesClearedPromise;
 
   ok(mockOfflineAppCacheHelper.clear.calledOnce, "Should clear app cache");
 
   status = getPersistentStoragePermStatus(TEST_ORIGIN);
   is(status, Ci.nsIPermissionManager.UNKNOWN_ACTION, "Should remove permission");
 
-  cacheUsage = yield getCacheUsage();
+  cacheUsage = yield cacheUsageGetter.get();
   quotaUsage = yield getQuotaUsage(TEST_ORIGIN);
   totalUsage = yield SiteDataManager.getTotalUsage();
   is(cacheUsage, 0, "The cahce usage should be removed");
   is(quotaUsage, 0, "The quota usage should be removed");
   is(totalUsage, 0, "The total usage should be removed");
   // Test accepting "Clear All Data" ends
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
--- a/browser/components/preferences/in-content/tests/site_data_test.html
+++ b/browser/components/preferences/in-content/tests/site_data_test.html
@@ -16,14 +16,14 @@
       request.onupgradeneeded = function(e) {
         let db = e.target.result;
         db.createObjectStore("TestStore", { keyPath: "id" });
       };
       request.onsuccess = function(e) {
         let db = e.target.result;
         let tx = db.transaction("TestStore", "readwrite");
         let store = tx.objectStore("TestStore");
+        tx.oncomplete = () => window.dispatchEvent(new Event("test-indexedDB-done"));
         store.put({ id: "test_id", description: "Site Data Test"});
-        window.dispatchEvent(new Event("test-indexedDB-done"));
       }
     </script>
   </body>
 </html>
--- a/browser/extensions/formautofill/bootstrap.js
+++ b/browser/extensions/formautofill/bootstrap.js
@@ -2,30 +2,86 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /* exported startup, shutdown, install, uninstall */
 
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+const STYLESHEET_URI = "chrome://formautofill/content/formautofill.css";
+const CACHED_STYLESHEETS = new WeakMap();
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillParent",
                                   "resource://formautofill/FormAutofillParent.jsm");
 
+function insertStyleSheet(domWindow, url) {
+  let doc = domWindow.document;
+  let styleSheetAttr = `href="${url}" type="text/css"`;
+  let styleSheet = doc.createProcessingInstruction("xml-stylesheet", styleSheetAttr);
+
+  doc.insertBefore(styleSheet, doc.documentElement);
+
+  if (CACHED_STYLESHEETS.has(domWindow)) {
+    CACHED_STYLESHEETS.get(domWindow).push(styleSheet);
+  } else {
+    CACHED_STYLESHEETS.set(domWindow, [styleSheet]);
+  }
+}
+
+let windowListener = {
+  onOpenWindow(aWindow) {
+    let domWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+
+    domWindow.addEventListener("load", function onWindowLoaded() {
+      insertStyleSheet(domWindow, STYLESHEET_URI);
+    }, {once: true});
+  },
+};
+
 function startup() {
   // Besides this pref, we'll need dom.forms.autocomplete.experimental enabled
   // as well to make sure form autocomplete works correctly.
   if (!Services.prefs.getBoolPref("browser.formautofill.experimental")) {
     return;
   }
 
   let parent = new FormAutofillParent();
+  let enumerator = Services.wm.getEnumerator("navigator:browser");
+  // Load stylesheet to already opened windows
+  while (enumerator.hasMoreElements()) {
+    let win = enumerator.getNext();
+    let domWindow = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+
+    insertStyleSheet(domWindow, STYLESHEET_URI);
+  }
+
+  Services.wm.addListener(windowListener);
+
   parent.init();
   Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true);
 }
 
-function shutdown() {}
+function shutdown() {
+  Services.wm.removeListener(windowListener);
+
+  let enumerator = Services.wm.getEnumerator("navigator:browser");
+
+  while (enumerator.hasMoreElements()) {
+    let win = enumerator.getNext();
+    let domWindow = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+    let cachedStyleSheets = CACHED_STYLESHEETS.get(domWindow);
+
+    if (!cachedStyleSheets) {
+      continue;
+    }
+
+    while (cachedStyleSheets.length !== 0) {
+      cachedStyleSheets.pop().remove();
+    }
+  }
+}
+
 function install() {}
 function uninstall() {}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/formautofill.css
@@ -0,0 +1,7 @@
+/* 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/. */
+
+.autocomplete-richlistitem[originaltype="autofill-profile"] {
+  -moz-binding: url("chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem");
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<bindings id="formautofillBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:html="http://www.w3.org/1999/xhtml"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="autocomplete-profile-listitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <xbl:content xmlns="http://www.w3.org/1999/xhtml">
+      <div anonid="profile-item-box" class="profile-item-box">
+        <div class="profile-label-col profile-item-col">
+          <span anonid="profile-label" class="profile-label"></span>
+        </div>
+        <div class="profile-comment-col profile-item-col">
+          <span anonid="profile-comment" class="profile-comment"></span>
+        </div>
+      </div>
+    </xbl:content>
+
+    <implementation implements="nsIDOMXULSelectControlItemElement">
+      <constructor>
+        <![CDATA[
+          this._itemBox = document.getAnonymousElementByAttribute(
+            this, "anonid", "profile-item-box"
+          );
+          this._label = document.getAnonymousElementByAttribute(
+            this, "anonid", "profile-label"
+          );
+          this._comment = document.getAnonymousElementByAttribute(
+            this, "anonid", "profile-comment"
+          );
+
+          this._adjustAcItem();
+        ]]>
+      </constructor>
+
+      <method name="_cleanup">
+        <body>
+        <![CDATA[
+            this._itemBox.removeAttribute("size");
+        ]]>
+        </body>
+      </method>
+
+      <method name="_onOverflow">
+        <body></body>
+      </method>
+
+      <method name="_onUnderflow">
+        <body></body>
+      </method>
+
+      <method name="_adjustAcItem">
+        <body>
+        <![CDATA[
+          let outerBoxRect = this.parentNode.getBoundingClientRect();
+          let value = this.getAttribute("ac-value");
+          let comment = this.getAttribute("ac-comment");
+
+          this._comment.textContent = comment;
+          this._label.textContent = value;
+
+          // Use two-lines layout when width is smaller than 150px
+          if (outerBoxRect.width <= 150) {
+            this._itemBox.setAttribute("size", "small");
+          } else {
+            this._itemBox.removeAttribute("size");
+          }
+        ]]>
+        </body>
+      </method>
+    </implementation>
+  </binding>
+
+</bindings>
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/824300.html
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+  <span style="filter: url(#f);">
+    <div style="transform: matrix(2, 1, 0, 2, 50, 50);"></div>
+  </span>
+
+  <svg xmlns="http://www.w3.org/2000/svg">
+    <filter id="f"/>
+  </svg>
+
+</body>
+</html>
\ No newline at end of file
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -427,16 +427,17 @@ load 795646.html
 skip load 802902.html # bug 901752
 load 806056-1.html
 load 806056-2.html
 load 812665.html
 load 813372-1.html
 load 817219.html
 load 818454.html
 load 822865.html
+load 824300.html
 asserts-if(stylo,1) load 824862.html # bug 1324704
 load 826163.html
 load 833604-1.html
 load 835056.html
 load 836990-1.html
 load 840480.html
 load 842114.html
 load 847242.html
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -6177,53 +6177,55 @@ nsDisplayTransform::GetDeltaToTransformO
   if (aBoundsOverride &&
       !(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
     refBox.Init(aBoundsOverride->Size());
   } else {
     refBox.Init(aFrame);
   }
 
   /* Allows us to access dimension getters by index. */
-  float coords[2];
+  float transformOrigin[2];
   TransformReferenceBox::DimensionGetter dimensionGetter[] =
     { &TransformReferenceBox::Width, &TransformReferenceBox::Height };
   TransformReferenceBox::DimensionGetter offsetGetter[] =
     { &TransformReferenceBox::X, &TransformReferenceBox::Y };
 
   for (uint8_t index = 0; index < 2; ++index) {
     /* If the transform-origin specifies a percentage, take the percentage
      * of the size of the box.
      */
-    const nsStyleCoord &coord = display->mTransformOrigin[index];
-    if (coord.GetUnit() == eStyleUnit_Calc) {
-      const nsStyleCoord::Calc *calc = coord.GetCalcValue();
-      coords[index] =
+    const nsStyleCoord& originValue  = display->mTransformOrigin[index];
+    if (originValue.GetUnit() == eStyleUnit_Calc) {
+      const nsStyleCoord::Calc *calc = originValue.GetCalcValue();
+      transformOrigin[index] =
         NSAppUnitsToFloatPixels((refBox.*dimensionGetter[index])(), aAppUnitsPerPixel) *
           calc->mPercent +
         NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
-    } else if (coord.GetUnit() == eStyleUnit_Percent) {
-      coords[index] =
+    } else if (originValue.GetUnit() == eStyleUnit_Percent) {
+      transformOrigin[index] =
         NSAppUnitsToFloatPixels((refBox.*dimensionGetter[index])(), aAppUnitsPerPixel) *
-        coord.GetPercentValue();
+        originValue.GetPercentValue();
     } else {
-      MOZ_ASSERT(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
-      coords[index] =
-        NSAppUnitsToFloatPixels(coord.GetCoordValue(), aAppUnitsPerPixel);
+      MOZ_ASSERT(originValue.GetUnit() == eStyleUnit_Coord,
+                 "unexpected unit");
+      transformOrigin[index] =
+        NSAppUnitsToFloatPixels(originValue.GetCoordValue(),
+                                aAppUnitsPerPixel);
     }
 
     if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
       // SVG frames (unlike other frames) have a reference box that can be (and
       // typically is) offset from the TopLeft() of the frame. We need to
       // account for that here.
-      coords[index] +=
+      transformOrigin[index] +=
         NSAppUnitsToFloatPixels((refBox.*offsetGetter[index])(), aAppUnitsPerPixel);
     }
   }
 
-  return Point3D(coords[0], coords[1],
+  return Point3D(transformOrigin[0], transformOrigin[1],
                  NSAppUnitsToFloatPixels(display->mTransformOrigin[2].GetCoordValue(),
                                          aAppUnitsPerPixel));
 }
 
 /* static */ bool
 nsDisplayTransform::ComputePerspectiveMatrix(const nsIFrame* aFrame,
                                              float aAppUnitsPerPixel,
                                              Matrix4x4& aOutMatrix)
@@ -6380,79 +6382,68 @@ nsDisplayTransform::GetResultingTransfor
     refBox.Init(frame);
   }
 
   /* Get the matrix, then change its basis to factor in the origin. */
   RuleNodeCacheConditions dummy;
   bool dummyBool;
   Matrix4x4 result;
   // Call IsSVGTransformed() regardless of the value of
-  // disp->mSpecifiedTransform, since we still need any transformFromSVGParent.
-  Matrix svgTransform, transformFromSVGParent;
+  // disp->mSpecifiedTransform, since we still need any
+  // parentsChildrenOnlyTransform.
+  Matrix svgTransform, parentsChildrenOnlyTransform;
   bool hasSVGTransforms =
-    frame && frame->IsSVGTransformed(&svgTransform, &transformFromSVGParent);
-  bool hasTransformFromSVGParent =
-    hasSVGTransforms && !transformFromSVGParent.IsIdentity();
+    frame && frame->IsSVGTransformed(&svgTransform,
+                                     &parentsChildrenOnlyTransform);
   /* Transformed frames always have a transform, or are preserving 3d (and might still have perspective!) */
   if (aProperties.mTransformList) {
     result = nsStyleTransformMatrix::ReadTransforms(aProperties.mTransformList->mHead,
                                                     frame ? frame->StyleContext() : nullptr,
                                                     frame ? frame->PresContext() : nullptr,
                                                     dummy, refBox, aAppUnitsPerPixel,
                                                     &dummyBool);
   } else if (hasSVGTransforms) {
     // Correct the translation components for zoom:
     float pixelsPerCSSPx = frame->PresContext()->AppUnitsPerCSSPixel() /
                              aAppUnitsPerPixel;
     svgTransform._31 *= pixelsPerCSSPx;
     svgTransform._32 *= pixelsPerCSSPx;
     result = Matrix4x4::From2D(svgTransform);
   }
 
+  // Apply any translation due to 'transform-origin' and/or 'transform-box':
+  result.ChangeBasis(aProperties.mToTransformOrigin);
+
+  // See the comment for nsSVGContainerFrame::HasChildrenOnlyTransform for
+  // an explanation of what children-only transforms are.
+  bool parentHasChildrenOnlyTransform =
+    hasSVGTransforms && !parentsChildrenOnlyTransform.IsIdentity();
+
+  if (parentHasChildrenOnlyTransform) {
+    float pixelsPerCSSPx =
+      frame->PresContext()->AppUnitsPerCSSPixel() / aAppUnitsPerPixel;
+    parentsChildrenOnlyTransform._31 *= pixelsPerCSSPx;
+    parentsChildrenOnlyTransform._32 *= pixelsPerCSSPx;
+
+    Point3D frameOffset(
+      NSAppUnitsToFloatPixels(-frame->GetPosition().x, aAppUnitsPerPixel),
+      NSAppUnitsToFloatPixels(-frame->GetPosition().y, aAppUnitsPerPixel),
+      0);
+    Matrix4x4 parentsChildrenOnlyTransform3D =
+      Matrix4x4::From2D(parentsChildrenOnlyTransform).ChangeBasis(frameOffset);
+
+    result *= parentsChildrenOnlyTransform3D;
+  }
 
   Matrix4x4 perspectiveMatrix;
   bool hasPerspective = aFlags & INCLUDE_PERSPECTIVE;
   if (hasPerspective) {
-    hasPerspective = ComputePerspectiveMatrix(frame, aAppUnitsPerPixel,
-                                              perspectiveMatrix);
-  }
-
-  if (!hasSVGTransforms || !hasTransformFromSVGParent) {
-    // This is a simplification of the following |else| block, the
-    // simplification being possible because we don't need to apply
-    // mToTransformOrigin between two transforms.
-    result.ChangeBasis(aProperties.mToTransformOrigin);
-  } else {
-    Point3D refBoxOffset(NSAppUnitsToFloatPixels(refBox.X(), aAppUnitsPerPixel),
-                         NSAppUnitsToFloatPixels(refBox.Y(), aAppUnitsPerPixel),
-                         0);
-    // We have both a transform and children-only transform. The
-    // 'transform-origin' must apply between the two, so we need to apply it
-    // now before we apply transformFromSVGParent. Since mToTransformOrigin is
-    // relative to the frame's TopLeft(), we need to convert it to SVG user
-    // space by subtracting refBoxOffset. (Then after applying
-    // transformFromSVGParent we have to reapply refBoxOffset below.)
-    result.ChangeBasis(aProperties.mToTransformOrigin - refBoxOffset);
-
-    // Now apply the children-only transforms, converting the translation
-    // components to device pixels:
-    float pixelsPerCSSPx =
-      frame->PresContext()->AppUnitsPerCSSPixel() / aAppUnitsPerPixel;
-    transformFromSVGParent._31 *= pixelsPerCSSPx;
-    transformFromSVGParent._32 *= pixelsPerCSSPx;
-    result = result * Matrix4x4::From2D(transformFromSVGParent);
-
-    // Similar to the code in the |if| block above, but since we've accounted
-    // for mToTransformOrigin so we don't include that. We also need to reapply
-    // refBoxOffset.
-    result.ChangeBasis(refBoxOffset);
-  }
-
-  if (hasPerspective) {
-    result = result * perspectiveMatrix;
+    if (ComputePerspectiveMatrix(frame, aAppUnitsPerPixel, perspectiveMatrix)) {
+      result *= perspectiveMatrix;
+    }
   }
 
   if ((aFlags & INCLUDE_PRESERVE3D_ANCESTORS) &&
       frame && frame->Combines3DTransformWithAncestors()) {
     // Include the transform set on our parent
     NS_ASSERTION(frame->GetParent() &&
                  frame->GetParent()->IsTransformed() &&
                  frame->GetParent()->Extend3DContext(),
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/pass.svg
@@ -0,0 +1,8 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
+  <title>Testcase reference file for generic pass condition</title>
+  <rect width="100%" height="100%" fill="lime"/>
+</svg>
--- a/layout/reftests/transform/reftest.list
+++ b/layout/reftests/transform/reftest.list
@@ -124,16 +124,17 @@ fuzzy-if(skiaContent,2,5) == stresstest-
 == table-2a.html table-2-ref.html
 == table-2b.html table-2-ref.html
 # Bug 722463
 == inline-1a.html inline-1-ref.html
 pref(svg.transform-box.enabled,true) == transform-box-svg-1a.svg transform-box-svg-1-ref.svg
 pref(svg.transform-box.enabled,true) == transform-box-svg-1b.svg transform-box-svg-1-ref.svg
 pref(svg.transform-box.enabled,true) == transform-box-svg-2a.svg transform-box-svg-2-ref.svg
 pref(svg.transform-box.enabled,true) == transform-box-svg-2b.svg transform-box-svg-2-ref.svg
+pref(svg.transform-box.enabled,true) == transform-box-svg-3a.svg pass.svg
 == transform-origin-svg-1a.svg transform-origin-svg-1-ref.svg
 == transform-origin-svg-1b.svg transform-origin-svg-1-ref.svg
 == transform-origin-svg-2a.svg transform-origin-svg-2-ref.svg
 == transform-origin-svg-2b.svg transform-origin-svg-2-ref.svg
 # Bug 1122526
 == animate-layer-scale-inherit-1.html animate-layer-scale-inherit-1-ref.html
 == animate-layer-scale-inherit-2.html animate-layer-scale-inherit-2-ref.html
 random-if(webrender) == animate-layer-scale-inherit-3.html animate-layer-scale-inherit-1-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-box-svg-3a.svg
@@ -0,0 +1,64 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="none">
+  <style>
+    /* <![CDATA[ */
+    .ref {
+      fill: red;
+    }
+    .test {
+      fill: lime;
+    }
+    /* ]]> */
+  </style>
+  <rect width="100%" height="100%" fill="lime"/>
+  <!-- all rect.test rects should be covered by the corresponded rest.ref rect-->
+  <!-- 1st row: transform-box only. -->
+  <rect class="ref"
+        x="11" y="11" width="8" height="8"/>
+  <rect class="test"
+        style="transform-box: view-box"
+        x="10" y="10" width="10" height="10"/>
+  <rect class="ref"
+        x="26" y="11" width="8" height="8"/>
+  <rect class="test"
+        style="transform-box: fill-box"
+        x="25" y="10" width="10" height="10"/>
+  <rect class="ref"
+        x="41" y="11" width="8" height="8"/>
+  <rect class="test"
+        style="transform-box: border-box"
+        x="40" y="10" width="10" height="10"/>
+
+  <!-- 2nd row: transform-box plus transform-origin. -->
+  <rect class="ref"
+        x="11" y="26" width="8" height="8"/>
+  <rect class="test"
+        style="transform-box: view-box; tansform-origin: 40% 60%"
+        x="10" y="25" width="10" height="10"/>
+  <rect class="ref"
+        x="26" y="26" width="8" height="8"/>
+  <rect class="test"
+        style="transform-box: fill-box; tansform-origin: 20px 10px"
+        x="25" y="25" width="10" height="10"/>
+  <rect class="ref"
+        x="41" y="26" width="8" height="8"/>
+  <rect class="test"
+        style="transform-box: border-box; tansform-origin: 40% 60%"
+        x="40" y="25" width="10" height="10"/>
+
+  <!-- 2nd row: transform-box, transform-origin and transform. -->
+  <rect class="ref"
+        x="11" y="46" width="8" height="8"/>
+  <rect class="test"
+        style="transform-box: fill-box; tansform-origin: 0% 0%; transform: scale(2);"
+        x="10" y="45" width="5" height="5"/>
+  <rect class="ref"
+        x="26" y="46" width="8" height="8"/>
+  <rect class="test"
+        style="transform-box: fill-box; transform-origin: 50% 50%; transform:rotate(45deg);"
+        x="24" y="44" width="12" height="12"/>
+  <rect class="ref"
+        x="41" y="46" width="8" height="8"/>
+  <rect class="test"
+        style="transform-box: border-box; tansform-origin: 100% 100%; transform: scale(1);"
+        x="40" y="45" width="10" height="10"/>
+</svg>
\ No newline at end of file
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -129,20 +129,28 @@ private:
       bool mightHaveNoneSVGMask =
         nsSVGEffects::GetEffectProperties(firstFrame).MightHaveNoneSVGMask();
 
       MOZ_ASSERT(mightHaveNoneSVGMask ||
                  aFrame->GetParent()->StyleContext()->GetPseudo() ==
                  nsCSSAnonBoxes::mozAnonymousBlock,
                  "How did we getting here, then?");
     }
+
+    bool hasEffect = nsSVGIntegrationUtils::UsingEffectsForFrame(aFrame);
+    nsOverflowAreas* preTransformOverflows =
+      aFrame->Properties().Get(aFrame->PreTransformOverflowAreasProperty());
+    // Having PreTransformOverflowAreasProperty means GetVisualOverflowRect()
+    // will return post-effect rect, which is not what we want, this function
+    // intentional reports pre-effect rect. But it does not matter if there is
+    // no SVG effect on this frame, since no effect means post-effect rect
+    // matches pre-effect rect.
+    MOZ_ASSERT(!hasEffect || !preTransformOverflows,
+               "GetVisualOverflowRect() won't return the pre-effects rect!");
 #endif
-    NS_ASSERTION(!aFrame->Properties().Get(
-                   aFrame->PreTransformOverflowAreasProperty()),
-                 "GetVisualOverflowRect() won't return the pre-effects rect!");
     return aFrame->GetVisualOverflowRect();
   }
 
   nsIFrame*     mFirstContinuation;
   nsIFrame*     mCurrentFrame;
   const nsRect& mCurrentFrameOverflowArea;
   nsRect        mResult;
   bool          mCheckPreEffectsBBoxPropCache;
--- a/netwerk/protocol/http/AlternateServices.cpp
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -111,18 +111,21 @@ AltSvcMapping::ProcessHeader(const nsCSt
         maxage = atoi(PromiseFlatCString(currentValue).get());
         break;
       } else {
         LOG(("Alt Svc ignoring parameter %s", currentName.BeginReading()));
       }
     }
 
     if (clearEntry) {
-      LOG(("Alt Svc clearing mapping for %s:%d", originHost.get(), originPort));
-      gHttpHandler->ConnMgr()->ClearHostMapping(originHost, originPort);
+      nsCString suffix;
+      originAttributes.CreateSuffix(suffix);
+      LOG(("Alt Svc clearing mapping for %s:%d:%s", originHost.get(),
+           originPort, suffix.get()));
+      gHttpHandler->ConnMgr()->ClearHostMapping(originHost, originPort, originAttributes);
       continue;
     }
 
     // unescape modifies a c string in place, so afterwards
     // update nsCString length
     nsUnescape(npnToken.BeginWriting());
     npnToken.SetLength(strlen(npnToken.BeginReading()));
 
@@ -135,52 +138,55 @@ AltSvcMapping::ProcessHeader(const nsCSt
     }
 
     RefPtr<AltSvcMapping> mapping = new AltSvcMapping(gHttpHandler->ConnMgr()->GetStoragePtr(),
                                                       gHttpHandler->ConnMgr()->StorageEpoch(),
                                                       originScheme,
                                                       originHost, originPort,
                                                       username, privateBrowsing,
                                                       NowInSeconds() + maxage,
-                                                      hostname, portno, npnToken);
+                                                      hostname, portno, npnToken,
+                                                      originAttributes);
     if (mapping->TTL() <= 0) {
       LOG(("Alt Svc invalid map"));
       mapping = nullptr;
       // since this isn't a parse error, let's clear any existing mapping
       // as that would have happened if we had accepted the parameters.
-      gHttpHandler->ConnMgr()->ClearHostMapping(originHost, originPort);
+      gHttpHandler->ConnMgr()->ClearHostMapping(originHost, originPort, originAttributes);
     } else {
       gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps,
                                             originAttributes);
     }
   }
 }
 
 AltSvcMapping::AltSvcMapping(DataStorage *storage, int32_t epoch,
                              const nsACString &originScheme,
                              const nsACString &originHost,
                              int32_t originPort,
                              const nsACString &username,
                              bool privateBrowsing,
                              uint32_t expiresAt,
                              const nsACString &alternateHost,
                              int32_t alternatePort,
-                             const nsACString &npnToken)
+                             const nsACString &npnToken,
+                             const OriginAttributes &originAttributes)
   : mStorage(storage)
   , mStorageEpoch(epoch)
   , mAlternateHost(alternateHost)
   , mAlternatePort(alternatePort)
   , mOriginHost(originHost)
   , mOriginPort(originPort)
   , mUsername(username)
   , mPrivate(privateBrowsing)
   , mExpiresAt(expiresAt)
   , mValidated(false)
   , mMixedScheme(false)
   , mNPNToken(npnToken)
+  , mOriginAttributes(originAttributes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_FAILED(SchemeIsHTTPS(originScheme, mHttps))) {
     LOG(("AltSvcMapping ctor %p invalid scheme\n", this));
     mExpiresAt = 0; // invalid
   }
 
@@ -201,41 +207,47 @@ AltSvcMapping::AltSvcMapping(DataStorage
 
   if ((mAlternatePort == mOriginPort) &&
       mAlternateHost.EqualsIgnoreCase(mOriginHost.get())) {
     LOG(("Alt Svc is also origin Svc - ignoring\n"));
     mExpiresAt = 0; // invalid
   }
 
   if (mExpiresAt) {
-    MakeHashKey(mHashKey, originScheme, mOriginHost, mOriginPort, mPrivate);
+    MakeHashKey(mHashKey, originScheme, mOriginHost, mOriginPort, mPrivate,
+                mOriginAttributes);
   }
 }
 
 void
 AltSvcMapping::MakeHashKey(nsCString &outKey,
                            const nsACString &originScheme,
                            const nsACString &originHost,
                            int32_t originPort,
-                           bool privateBrowsing)
+                           bool privateBrowsing,
+                           const OriginAttributes &originAttributes)
 {
   outKey.Truncate();
 
   if (originPort == -1) {
     bool isHttps = originScheme.Equals("https");
     originPort = isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
   }
 
   outKey.Append(originScheme);
   outKey.Append(':');
   outKey.Append(originHost);
   outKey.Append(':');
   outKey.AppendInt(originPort);
   outKey.Append(':');
   outKey.Append(privateBrowsing ? 'P' : '.');
+  outKey.Append(':');
+  nsAutoCString suffix;
+  originAttributes.CreateSuffix(suffix);
+  outKey.Append(suffix);
 }
 
 int32_t
 AltSvcMapping::TTL()
 {
   return mExpiresAt - NowInSeconds();
 }
 
@@ -348,16 +360,20 @@ AltSvcMapping::Serialize(nsCString &out)
   out.Append(mNPNToken);
   out.Append(':');
   out.Append(mValidated ? 'y' : 'n');
   out.Append(':');
   out.AppendInt(mStorageEpoch);
   out.Append(':');
   out.Append(mMixedScheme ? 'y' : 'n');
   out.Append(':');
+  nsAutoCString suffix;
+  mOriginAttributes.CreateSuffix(suffix);
+  out.Append(suffix);
+  out.Append(':');
 }
 
 AltSvcMapping::AltSvcMapping(DataStorage *storage, int32_t epoch, const nsCString &str)
   : mStorage(storage)
   , mStorageEpoch(epoch)
 {
   mValidated = false;
   nsresult code;
@@ -389,20 +405,22 @@ COMPILER ERROR
     _NS_NEXT_TOKEN;
     mNPNToken = Substring(str, start, idx - start);
     _NS_NEXT_TOKEN;
     mValidated = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("y"));
     _NS_NEXT_TOKEN;
     mStorageEpoch = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
     _NS_NEXT_TOKEN;
     mMixedScheme = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("y"));
+    _NS_NEXT_TOKEN;
+    Unused << mOriginAttributes.PopulateFromSuffix(Substring(str, start, idx - start));
     #undef _NS_NEXT_TOKEN
 
     MakeHashKey(mHashKey, mHttps ? NS_LITERAL_CSTRING("https") : NS_LITERAL_CSTRING("http"),
-                mOriginHost, mOriginPort, mPrivate);
+                mOriginHost, mOriginPort, mPrivate, mOriginAttributes);
   } while (false);
 }
 
 // This is the asynchronous null transaction used to validate
 // an alt-svc advertisement only for https://
 class AltSvcTransaction final : public NullHttpTransaction
 {
 public:
@@ -908,17 +926,18 @@ AltSvcCache::UpdateAltServiceMapping(Alt
       // object deletes itself when done if started
       LOG(("AltSvcCache::UpdateAltServiceMapping %p .wk checker started %p\n", this, checker));
     }
   }
 }
 
 already_AddRefed<AltSvcMapping>
 AltSvcCache::GetAltServiceMapping(const nsACString &scheme, const nsACString &host,
-                                  int32_t port, bool privateBrowsing)
+                                  int32_t port, bool privateBrowsing,
+                                  const OriginAttributes &originAttributes)
 {
   bool isHTTPS;
   MOZ_ASSERT(NS_IsMainThread());
   if (!mStorage) {
     // DataStorage gives synchronous access to a memory based hash table
     // that is backed by disk where those writes are done asynchronously
     // on another thread
     mStorage = DataStorage::Get(NS_LITERAL_STRING("AlternateServices.txt"));
@@ -940,87 +959,95 @@ AltSvcCache::GetAltServiceMapping(const 
   if (!gHttpHandler->AllowAltSvc()) {
     return nullptr;
   }
   if (!gHttpHandler->AllowAltSvcOE() && !isHTTPS) {
     return nullptr;
   }
 
   nsAutoCString key;
-  AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing);
+  AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing, originAttributes);
   RefPtr<AltSvcMapping> existing = LookupMapping(key, privateBrowsing);
   LOG(("AltSvcCache::GetAltServiceMapping %p key=%s "
        "existing=%p validated=%d ttl=%d",
        this, key.get(), existing.get(), existing ? existing->Validated() : 0,
        existing ? existing->TTL() : 0));
   if (existing && !existing->Validated()) {
     existing = nullptr;
   }
   return existing.forget();
 }
 
 class ProxyClearHostMapping : public Runnable {
 public:
-  explicit ProxyClearHostMapping(const nsACString &host, int32_t port)
+  explicit ProxyClearHostMapping(const nsACString &host, int32_t port,
+                                 const OriginAttributes &originAttributes)
     : mHost(host)
     , mPort(port)
+    , mOriginAttributes(originAttributes)
     {}
 
     NS_IMETHOD Run() override
     {
       MOZ_ASSERT(NS_IsMainThread());
-      gHttpHandler->ConnMgr()->ClearHostMapping(mHost, mPort);
+      gHttpHandler->ConnMgr()->ClearHostMapping(mHost, mPort, mOriginAttributes);
       return NS_OK;
     }
 private:
     nsCString mHost;
     int32_t mPort;
+    OriginAttributes mOriginAttributes;
 };
 
 void
-AltSvcCache::ClearHostMapping(const nsACString &host, int32_t port)
+AltSvcCache::ClearHostMapping(const nsACString &host, int32_t port,
+                              const OriginAttributes &originAttributes)
 {
   if (!NS_IsMainThread()) {
-    nsCOMPtr<nsIRunnable> event = new ProxyClearHostMapping(host, port);
+    nsCOMPtr<nsIRunnable> event = new ProxyClearHostMapping(host, port, originAttributes);
     if (event) {
       NS_DispatchToMainThread(event);
     }
     return;
   }
   nsAutoCString key;
-  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, true);
+  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, true,
+                             originAttributes);
   RefPtr<AltSvcMapping> existing = LookupMapping(key, true);
   if (existing) {
     existing->SetExpired();
   }
 
-  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, true);
+  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, true,
+                             originAttributes);
   existing = LookupMapping(key, true);
   if (existing) {
     existing->SetExpired();
   }
 
-  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, false);
+  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, false,
+                             originAttributes);
   existing = LookupMapping(key, false);
   if (existing) {
     existing->SetExpired();
   }
 
-  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, false);
+  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, false,
+                             originAttributes);
   existing = LookupMapping(key, false);
   if (existing) {
     existing->SetExpired();
   }
 }
 
 void
 AltSvcCache::ClearHostMapping(nsHttpConnectionInfo *ci)
 {
   if (!ci->GetOrigin().IsEmpty()) {
-    ClearHostMapping(ci->GetOrigin(), ci->OriginPort());
+    ClearHostMapping(ci->GetOrigin(), ci->OriginPort(), ci->GetOriginAttributes());
   }
 }
 
 void
 AltSvcCache::ClearAltServiceMappings()
 {
     MOZ_ASSERT(NS_IsMainThread());
     if (mStorage) {
--- a/netwerk/protocol/http/AlternateServices.h
+++ b/netwerk/protocol/http/AlternateServices.h
@@ -50,17 +50,18 @@ private: // ctor from ProcessHeader
                 const nsACString &originScheme,
                 const nsACString &originHost,
                 int32_t originPort,
                 const nsACString &username,
                 bool privateBrowsing,
                 uint32_t expiresAt,
                 const nsACString &alternateHost,
                 int32_t alternatePort,
-                const nsACString &npnToken);
+                const nsACString &npnToken,
+                const OriginAttributes &originAttributes);
 public:
   AltSvcMapping(DataStorage *storage, int32_t storageEpoch, const nsCString &serialized);
 
   static void ProcessHeader(const nsCString &buf, const nsCString &originScheme,
                             const nsCString &originHost, int32_t originPort,
                             const nsACString &username, bool privateBrowsing,
                             nsIInterfaceRequestor *callbacks, nsProxyInfo *proxyInfo,
                             uint32_t caps, const OriginAttributes &originAttributes);
@@ -87,17 +88,18 @@ public:
   void SetExpiresAt(int32_t val);
   void SetExpired();
   void Sync();
 
   static void MakeHashKey(nsCString &outKey,
                           const nsACString &originScheme,
                           const nsACString &originHost,
                           int32_t originPort,
-                          bool privateBrowsing);
+                          bool privateBrowsing,
+                          const OriginAttributes &originAttributes);
 
 private:
   virtual ~AltSvcMapping() {};
   void     SyncString(const nsCString& val);
   RefPtr<DataStorage> mStorage;
   int32_t             mStorageEpoch;
   void Serialize (nsCString &out);
 
@@ -115,16 +117,18 @@ private:
 
   MOZ_INIT_OUTSIDE_CTOR uint32_t mExpiresAt; // alt-svc mappping
 
   MOZ_INIT_OUTSIDE_CTOR bool mValidated;
   MOZ_INIT_OUTSIDE_CTOR bool mHttps; // origin is https://
   MOZ_INIT_OUTSIDE_CTOR bool mMixedScheme; // .wk allows http and https on same con
 
   nsCString mNPNToken;
+
+  OriginAttributes mOriginAttributes;
 };
 
 class AltSvcOverride : public nsIInterfaceRequestor
                      , public nsISpeculativeConnectionOverrider
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
@@ -167,19 +171,20 @@ class AltSvcCache
 public:
   AltSvcCache() : mStorageEpoch(0) {}
   virtual ~AltSvcCache () {};
   void UpdateAltServiceMapping(AltSvcMapping *map, nsProxyInfo *pi,
                                nsIInterfaceRequestor *, uint32_t caps,
                                const OriginAttributes &originAttributes); // main thread
   already_AddRefed<AltSvcMapping> GetAltServiceMapping(const nsACString &scheme,
                                                        const nsACString &host,
-                                                       int32_t port, bool pb);
+                                                       int32_t port, bool pb,
+                                                       const OriginAttributes &originAttributes);
   void ClearAltServiceMappings();
-  void ClearHostMapping(const nsACString &host, int32_t port);
+  void ClearHostMapping(const nsACString &host, int32_t port, const OriginAttributes &originAttributes);
   void ClearHostMapping(nsHttpConnectionInfo *ci);
   DataStorage *GetStoragePtr() { return mStorage.get(); }
   int32_t      StorageEpoch()  { return mStorageEpoch; }
 
 private:
   already_AddRefed<AltSvcMapping> LookupMapping(const nsCString &key, bool privateBrowsing);
   RefPtr<DataStorage>             mStorage;
   int32_t                         mStorageEpoch;
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -5944,17 +5944,18 @@ nsHttpChannel::BeginConnect()
     RefPtr<AltSvcMapping> mapping;
     if (!mConnectionInfo && mAllowAltSvc && // per channel
         !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
         (scheme.Equals(NS_LITERAL_CSTRING("http")) ||
          scheme.Equals(NS_LITERAL_CSTRING("https"))) &&
         (!proxyInfo || proxyInfo->IsDirect()) &&
         (mapping = gHttpHandler->GetAltServiceMapping(scheme,
                                                       host, port,
-                                                      mPrivateBrowsing))) {
+                                                      mPrivateBrowsing,
+                                                      originAttributes))) {
         LOG(("nsHttpChannel %p Alt Service Mapping Found %s://%s:%d [%s]\n",
              this, scheme.get(), mapping->AlternateHost().get(),
              mapping->AlternatePort(), mapping->HashKey().get()));
 
         if (!(mLoadFlags & LOAD_ANONYMOUS) && !mPrivateBrowsing) {
             nsAutoCString altUsedLine(mapping->AlternateHost());
             bool defaultPort = mapping->AlternatePort() ==
                 (isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT);
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -248,19 +248,20 @@ public:
                                  const OriginAttributes &originAttributes)
     {
         mConnMgr->UpdateAltServiceMapping(map, proxyInfo, callbacks, caps,
                                           originAttributes);
     }
 
     already_AddRefed<AltSvcMapping> GetAltServiceMapping(const nsACString &scheme,
                                                          const nsACString &host,
-                                                         int32_t port, bool pb)
+                                                         int32_t port, bool pb,
+                                                         const OriginAttributes &originAttributes)
     {
-        return mConnMgr->GetAltServiceMapping(scheme, host, port, pb);
+        return mConnMgr->GetAltServiceMapping(scheme, host, port, pb, originAttributes);
     }
 
     //
     // The HTTP handler caches pointers to specific XPCOM services, and
     // provides the following helper routines for accessing those services:
     //
     nsresult GetStreamConverterService(nsIStreamConverterService **);
     nsresult GetIOService(nsIIOService** service);
--- a/netwerk/test/unit/test_altsvc.js
+++ b/netwerk/test/unit/test_altsvc.js
@@ -140,16 +140,17 @@ function makeChan(origin) {
 
 var origin;
 var xaltsvc;
 var retryCounter = 0;
 var loadWithoutClearingMappings = false;
 var nextTest;
 var expectPass = true;
 var waitFor = 0;
+var originAttributes = {};
 
 var Listener = function() {};
 Listener.prototype = {
   onStartRequest: function testOnStartRequest(request, ctx) {
     do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
 
     if (expectPass) {
       if (!Components.isSuccessCode(request.status)) {
@@ -217,16 +218,17 @@ function doTest()
   }
   if (loadWithoutClearingMappings) {
     chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
   } else {
     chan.loadFlags = Ci.nsIRequest.LOAD_FRESH_CONNECTION |
                      Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
   }
   loadWithoutClearingMappings = false;
+  chan.loadInfo.originAttributes = originAttributes;
   chan.asyncOpen2(listener);
 }
 
 // xaltsvc is overloaded to do two things..
 // 1] it is sent in the x-altsvc request header, and the response uses the value in the Alt-Svc response header
 // 2] the test polls until necko sets Alt-Used to that value (i.e. it uses that route)
 //
 // When xaltsvc is set to h2Route (i.e. :port with the implied hostname) it doesn't match the alt-used,
@@ -362,13 +364,80 @@ function doTest10()
 // is served
 function doTest11()
 {
   dump("doTest11()\n");
   origin = httpBarOrigin;
   xaltsvc = h2FooRoute;
   expectPass = true;
   waitFor = 500;
-  nextTest = testsDone;
+  nextTest = doTest12;
   do_test_pending();
   doTest();
 }
 
+// Test 12-15:
+// Insert a cache of http://foo served from h2=:port with origin attributes.
+function doTest12()
+{
+  dump("doTest12()\n");
+  origin = httpFooOrigin;
+  xaltsvc = h2Route;
+  originAttributes = {
+    userContextId: 1,
+    firstPartyDomain: "a.com",
+  };
+  nextTest = doTest13;
+  do_test_pending();
+  doTest();
+  xaltsvc = h2FooRoute;
+}
+
+// Make sure we get a cache miss with a different userContextId.
+function doTest13()
+{
+  dump("doTest13()\n");
+  origin = httpFooOrigin;
+  xaltsvc = 'NA';
+  originAttributes = {
+    userContextId: 2,
+    firstPartyDomain: "a.com",
+  };
+  loadWithoutClearingMappings = true;
+  nextTest = doTest14;
+  do_test_pending();
+  doTest();
+}
+
+// Make sure we get a cache miss with a different firstPartyDomain.
+function doTest14()
+{
+  dump("doTest14()\n");
+  origin = httpFooOrigin;
+  xaltsvc = 'NA';
+  originAttributes = {
+    userContextId: 1,
+    firstPartyDomain: "b.com",
+  };
+  loadWithoutClearingMappings = true;
+  nextTest = doTest15;
+  do_test_pending();
+  doTest();
+}
+//
+// Make sure we get a cache hit with the same origin attributes.
+function doTest15()
+{
+  dump("doTest15()\n");
+  origin = httpFooOrigin;
+  xaltsvc = 'NA';
+  originAttributes = {
+    userContextId: 1,
+    firstPartyDomain: "a.com",
+  };
+  loadWithoutClearingMappings = true;
+  nextTest = testsDone;
+  do_test_pending();
+  doTest();
+  // This ensures a cache hit.
+  xaltsvc = h2FooRoute;
+}
+
--- a/netwerk/wifi/nsWifiScannerDBus.cpp
+++ b/netwerk/wifi/nsWifiScannerDBus.cpp
@@ -10,30 +10,35 @@ namespace mozilla {
 
 nsWifiScannerDBus::nsWifiScannerDBus(nsCOMArray<nsWifiAccessPoint> *aAccessPoints)
 : mAccessPoints(aAccessPoints)
 {
   MOZ_ASSERT(mAccessPoints);
 
   mConnection =
     already_AddRefed<DBusConnection>(dbus_bus_get(DBUS_BUS_SYSTEM, nullptr));
-  MOZ_ASSERT(mConnection);
-  dbus_connection_set_exit_on_disconnect(mConnection, false);
+
+  if (mConnection) {
+    dbus_connection_set_exit_on_disconnect(mConnection, false);
+  }
 
   MOZ_COUNT_CTOR(nsWifiScannerDBus);
 }
 
 nsWifiScannerDBus::~nsWifiScannerDBus()
 {
   MOZ_COUNT_DTOR(nsWifiScannerDBus);
 }
 
 nsresult
 nsWifiScannerDBus::Scan()
 {
+  if (!mConnection) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
   return SendMessage("org.freedesktop.NetworkManager",
                      "/org/freedesktop/NetworkManager",
                      "GetDevices");
 }
 
 nsresult
 nsWifiScannerDBus::SendMessage(const char* aInterface,
                                const char* aPath,
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1299,92 +1299,92 @@ extends="chrome://global/content/binding
         <body>
           <![CDATA[
           var controller = this.mInput.controller;
           var matchCount = this._matchCount;
           var existingItemsCount = this.richlistbox.childNodes.length;
 
           // Process maxRows per chunk to improve performance and user experience
           for (let i = 0; i < this.maxRows; i++) {
-            if (this._currentIndex >= matchCount)
+            if (this._currentIndex >= matchCount) {
               break;
-
-            var item;
-
-            // trim the leading/trailing whitespace
-            var trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, "");
-
-            let url = controller.getValueAt(this._currentIndex);
-
-            if (this._currentIndex < existingItemsCount) {
-              // re-use the existing item
-              item = this.richlistbox.childNodes[this._currentIndex];
-              item.setAttribute("dir", this.style.direction);
+            }
+            let item;
+            let reusable = false;
+            let itemExists = this._currentIndex < existingItemsCount;
 
-              // Completely reuse the existing richlistitem for invalidation
-              // due to new results, but only when: the item is the same, *OR*
-              // we are about to replace the currently mouse-selected item, to
-              // avoid surprising the user.
-              let iface = Components.interfaces.nsIAutoCompletePopup;
-              if (item.getAttribute("text") == trimmedSearchString &&
-                  invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
-                  (item.getAttribute("url") == url ||
-                   this.richlistbox.mouseSelectedIndex === this._currentIndex)) {
-                // Additionally, if the item is a searchengine action, then it
-                // should only be reused if the engine name is the same as the
-                // popup's override engine name, if any.
-                let action = item._parseActionUrl(url);
-                if (!action ||
-                    action.type != "searchengine" ||
-                    !this.overrideSearchEngineName ||
-                    action.params.engineName == this.overrideSearchEngineName) {
-                  item.collapsed = false;
-                  // Call adjustSiteIconStart only after setting collapsed=
-                  // false.  The calculations it does may be wrong otherwise.
-                  item.adjustSiteIconStart(this._siteIconStart);
-                  // The popup may have changed size between now and the last
-                  // time the item was shown, so always handle over/underflow.
-                  item.handleOverUnderflow();
-                  this._currentIndex++;
-                  continue;
-                }
-              }
+            let originalValue, originalText;
+            let value = controller.getValueAt(this._currentIndex);
+            let label = controller.getLabelAt(this._currentIndex);
+            let comment = controller.getCommentAt(this._currentIndex);
+            let style = controller.getStyleAt(this._currentIndex);
+            let image = controller.getImageAt(this._currentIndex);
+            // trim the leading/trailing whitespace
+            let trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, "");
+
+            if (itemExists) {
+              item = this.richlistbox.childNodes[this._currentIndex];
+
+              originalValue = item.getAttribute("ac-value");
+              originalText = item.getAttribute("ac-text");
+
+              reusable = item.getAttribute("originaltype") === style;
             } else {
               // need to create a new item
               item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
-              item.setAttribute("dir", this.style.direction);
             }
 
-            // set these attributes before we set the class
-            // so that we can use them from the constructor
-            let iconURI = controller.getImageAt(this._currentIndex);
-            item.setAttribute("image", iconURI);
-            item.setAttribute("url", url);
-            item.setAttribute("title", controller.getCommentAt(this._currentIndex));
-            item.setAttribute("originaltype", controller.getStyleAt(this._currentIndex));
-            item.setAttribute("text", trimmedSearchString);
+            item.setAttribute("dir", this.style.direction);
+            item.setAttribute("ac-image", image);
+            item.setAttribute("ac-value", value);
+            item.setAttribute("ac-label", label);
+            item.setAttribute("ac-comment", comment);
+            item.setAttribute("ac-text", trimmedSearchString);
 
-            if (this._currentIndex < existingItemsCount) {
-              // re-use the existing item
+            // Completely reuse the existing richlistitem for invalidation
+            // due to new results, but only when: the item is the same, *OR*
+            // we are about to replace the currently mouse-selected item, to
+            // avoid surprising the user.
+            let iface = Components.interfaces.nsIAutoCompletePopup;
+            if (reusable &&
+                originalText == trimmedSearchString &&
+                invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
+                (originalValue == value ||
+                 this.richlistbox.mouseSelectedIndex === this._currentIndex)) {
+
+              // try to re-use the existing item
+              let reused = item._reuseAcItem();
+              if (reused) {
+                this._currentIndex++;
+                continue;
+              }
+            } else {
+              if (typeof item._cleanup == "function") {
+                item._cleanup();
+              }
+
+              item.setAttribute("originaltype", style);
+            }
+
+            if (itemExists) {
               item._adjustAcItem();
               item.collapsed = false;
             } else {
               // set the class at the end so we can use the attributes
               // in the xbl constructor
               item.className = "autocomplete-richlistitem";
               this.richlistbox.appendChild(item);
             }
 
-            // The binding may have not been applied yet.
-            setTimeout(() => {
-              let changed = item.adjustSiteIconStart(this._siteIconStart);
-              if (changed) {
-                item.handleOverUnderflow();
-              }
-            }, 0);
+            if (typeof item.onChanged == "function") {
+              // The binding may have not been applied yet.
+              setTimeout(() => {
+                item.onChanged();
+              }, 0);
+            }
 
             this._currentIndex++;
           }
 
           if (typeof this.onResultsAdded == "function")
             this.onResultsAdded();
 
           if (this._currentIndex < matchCount) {
@@ -1641,16 +1641,28 @@ extends="chrome://global/content/binding
           );
           this._actionText = document.getAnonymousElementByAttribute(
             this, "anonid", "action-text"
           );
           this._adjustAcItem();
         ]]>
       </constructor>
 
+      <method name="_cleanup">
+        <body>
+        <![CDATA[
+          this.removeAttribute("url");
+          this.removeAttribute("image");
+          this.removeAttribute("title");
+          this.removeAttribute("text");
+          this.removeAttribute("displayurl");
+        ]]>
+        </body>
+      </method>
+
       <property name="label" readonly="true">
         <getter>
           <![CDATA[
             // This property is a string that is read aloud by screen readers,
             // so it must not contain anything that should not be user-facing.
 
             let parts = [
               this.getAttribute("title"),
@@ -2027,19 +2039,68 @@ extends="chrome://global/content/binding
               Components.classes["@mozilla.org/intl/texttosuburi;1"]
                         .getService(Components.interfaces.nsITextToSubURI);
           }
           return this._textToSubURI.unEscapeURIForUI("UTF-8", url);
           ]]>
         </body>
       </method>
 
+      <method name="_onChanged">
+        <body>
+          <![CDATA[
+            let iconChanged = item.adjustSiteIconStart(this.popup._siteIconStart);
+
+            if (iconChanged) {
+              item.handleOverUnderflow();
+            }
+          ]]>
+        </body>
+      </method>
+
+
+      <method name="_reuseAcItem">
+        <body>
+          <![CDATA[
+            let action = this._parseActionUrl(this.getAttribute("url"));
+            let popup = this.parentNode.parentNode;
+
+            // If the item is a searchengine action, then it should
+            // only be reused if the engine name is the same as the
+            // popup's override engine name, if any.
+            if (!action ||
+                action.type != "searchengine" ||
+                !popup.overrideSearchEngineName ||
+                action.params.engineName == popup.overrideSearchEngineName) {
+
+              this.collapsed = false;
+              // Call adjustSiteIconStart only after setting collapsed=
+              // false.  The calculations it does may be wrong otherwise.
+              this.adjustSiteIconStart(popup._siteIconStart);
+              // The popup may have changed size between now and the last
+              // time the item was shown, so always handle over/underflow.
+              this.handleOverUnderflow();
+
+              return true;
+            }
+
+            return false;
+          ]]>
+        </body>
+      </method>
+
+
       <method name="_adjustAcItem">
         <body>
           <![CDATA[
+          this.setAttribute("url", this.getAttribute("ac-value"));
+          this.setAttribute("image", this.getAttribute("ac-image"));
+          this.setAttribute("title", this.getAttribute("ac-comment"));
+          this.setAttribute("text", this.getAttribute("ac-text"));
+
           let popup = this.parentNode.parentNode;
           if (!popup.popupOpen) {
             // Removing the max-width and resetting it later when overflow is
             // handled is jarring when the item is visible, so skip this when
             // the popup is open.
             this._removeMaxWidths();
           }
 
--- a/toolkit/content/widgets/datepicker.js
+++ b/toolkit/content/widgets/datepicker.js
@@ -15,16 +15,20 @@ function DatePicker(context) {
   DatePicker.prototype = {
     /**
      * Initializes the date picker. Set the default states and properties.
      * @param  {Object} props
      *         {
      *           {Number} year [optional]
      *           {Number} month [optional]
      *           {Number} date [optional]
+     *           {Number} firstDayOfWeek
+     *           {Array<Number>} weekends
+     *           {Array<String>} monthStrings
+     *           {Array<String>} weekdayStrings
      *           {String} locale [optional]: User preferred locale
      *         }
      */
     init(props = {}) {
       this.props = props;
       this._setDefaultState();
       this._createComponents();
       this._update();
@@ -35,16 +39,18 @@ function DatePicker(context) {
      */
     _setDefaultState() {
       const now = new Date();
       const { year = now.getFullYear(),
               month = now.getMonth(),
               day = now.getDate(),
               firstDayOfWeek,
               weekends,
+              monthStrings,
+              weekdayStrings,
               locale } = this.props;
       const dateKeeper = new DateKeeper({
         year, month, day
       }, {
         firstDayOfWeek,
         weekends,
         calViewSize: CAL_VIEW_SIZE
       });
@@ -52,18 +58,18 @@ function DatePicker(context) {
       this.state = {
         dateKeeper,
         locale,
         isMonthPickerVisible: false,
         isYearSet: false,
         isMonthSet: false,
         isDateSet: false,
         getDayString: new Intl.NumberFormat(locale).format,
-        // TODO: use calendar terms when available (Bug 1287677)
-        getWeekHeaderString: weekday => ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][weekday],
+        getWeekHeaderString: weekday => weekdayStrings[weekday],
+        getMonthString: month => monthStrings[month],
         setValue: ({ dateValue, selectionValue }) => {
           dateKeeper.setValue(dateValue);
           this.state.selectionValue = selectionValue;
           this.state.isYearSet = true;
           this.state.isMonthSet = true;
           this.state.isDateSet = true;
           this._update();
           this._dispatchState();
@@ -98,16 +104,17 @@ function DatePicker(context) {
           locale: this.state.locale
         }, {
           weekHeader: this.context.weekHeader,
           daysView: this.context.daysView
         }),
         monthYear: new MonthYear({
           setYear: this.state.setYear,
           setMonth: this.state.setMonth,
+          getMonthString: this.state.getMonthString,
           locale: this.state.locale
         }, {
           monthYear: this.context.monthYear,
           monthYearView: this.context.monthYearView
         })
       };
     },
 
@@ -274,35 +281,35 @@ function DatePicker(context) {
   /**
    * MonthYear is a component that handles the month & year spinners
    *
    * @param {Object} options
    *        {
    *          {String} locale
    *          {Function} setYear
    *          {Function} setMonth
+   *          {Function} getMonthString
    *        }
    * @param {DOMElement} context
    */
   function MonthYear(options, context) {
     const spinnerSize = 5;
-    const monthFormat = new Intl.DateTimeFormat(options.locale, { month: "short", timeZone: "UTC" }).format;
     const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric" }).format;
     const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric", month: "long" }).format;
 
     this.context = context;
     this.state = { dateFormat };
     this.props = {};
     this.components = {
       month: new Spinner({
         setValue: month => {
           this.state.isMonthSet = true;
           options.setMonth(month);
         },
-        getDisplayString: month => monthFormat(new Date(Date.UTC(0, month))),
+        getDisplayString: options.getMonthString,
         viewportSize: spinnerSize
       }, context.monthYearView),
       year: new Spinner({
         setValue: year => {
           this.state.isYearSet = true;
           options.setYear(year);
         },
         getDisplayString: year => yearFormat(new Date(new Date(0).setFullYear(year))),
--- a/toolkit/content/widgets/datetimepopup.xml
+++ b/toolkit/content/widgets/datetimepopup.xml
@@ -17,16 +17,23 @@
     <implementation>
       <field name="dateTimePopupFrame">
         this.querySelector("#dateTimePopupFrame");
       </field>
       <field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field>
       <field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
       <field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field>
       <field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field>
+      <constructor><![CDATA[
+        this.l10n = {};
+        const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
+                          .getService(Components.interfaces.mozIMozIntl);
+        mozIntl.addGetCalendarInfo(l10n);
+        mozIntl.addGetDisplayNames(l10n);
+      ]]></constructor>
       <method name="loadPicker">
         <parameter name="type"/>
         <parameter name="detail"/>
         <body><![CDATA[
           this.hidden = false;
           this.type = type;
           this.pickerState = {};
           // TODO: Resize picker according to content zoom level
@@ -112,26 +119,53 @@
                 }
               });
               break;
             }
             case "date": {
               const { year, month, day } = detail.value;
               const { firstDayOfWeek, weekends } =
                 this.getCalendarInfo(locale);
+              const monthStrings = this.getDisplayNames(
+                locale, [
+                  "dates/gregorian/months/january",
+                  "dates/gregorian/months/february",
+                  "dates/gregorian/months/march",
+                  "dates/gregorian/months/april",
+                  "dates/gregorian/months/may",
+                  "dates/gregorian/months/june",
+                  "dates/gregorian/months/july",
+                  "dates/gregorian/months/august",
+                  "dates/gregorian/months/september",
+                  "dates/gregorian/months/october",
+                  "dates/gregorian/months/november",
+                  "dates/gregorian/months/december",
+                ], "short");
+              const weekdayStrings = this.getDisplayNames(
+                locale, [
+                  "dates/gregorian/weekdays/sunday",
+                  "dates/gregorian/weekdays/monday",
+                  "dates/gregorian/weekdays/tuesday",
+                  "dates/gregorian/weekdays/wednesday",
+                  "dates/gregorian/weekdays/thursday",
+                  "dates/gregorian/weekdays/friday",
+                  "dates/gregorian/weekdays/saturday",
+                ], "short");
 
               this.postMessageToPicker({
                 name: "PickerInit",
                 detail: {
                   year,
                   // Month value from input box starts from 1 instead of 0
                   month: month == undefined ? undefined : month - 1,
                   day,
                   firstDayOfWeek,
                   weekends,
+                  monthStrings,
+                  weekdayStrings,
                   locale
                 }
               });
               break;
             }
           }
         ]]></body>
       </method>
@@ -187,21 +221,17 @@
               break;
             }
           }
         ]]></body>
       </method>
       <method name="getCalendarInfo">
         <parameter name="locale"/>
         <body><![CDATA[
-          const l10n = {};
-          const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
-                            .getService(Components.interfaces.mozIMozIntl);
-          mozIntl.addGetCalendarInfo(l10n);
-          const calendarInfo = l10n.getCalendarInfo(locale);
+          const calendarInfo = this.l10n.getCalendarInfo(locale);
 
           // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
           // so they need to be mapped to JavaScript convention with 0 as Sunday
           // and 6 as Saturday
           let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
               weekendStart = calendarInfo.weekendStart - 1,
               weekendEnd = calendarInfo.weekendEnd - 1;
 
@@ -219,16 +249,25 @@
           }
 
           return {
             firstDayOfWeek,
             weekends
           }
         ]]></body>
       </method>
+      <method name="getDisplayNames">
+        <parameter name="locale"/>
+        <parameter name="keys"/>
+        <parameter name="style"/>
+        <body><![CDATA[
+          const displayNames = this.l10n.getDisplayNames(locale, {keys, style});
+          return keys.map(key => displayNames.values[key]);
+        ]]></body>
+      </method>
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
           switch (aEvent.type) {
             case "load": {
               this.initPicker(this.detail);
               this.dateTimePopupFrame.contentWindow.addEventListener("message", this);
               break;
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -14,16 +14,26 @@
 #include "mozilla/Mutex.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/TypeTraits.h"
 
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 
+#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
+#define PROMISE_DEBUG
+#endif
+
+#ifdef PROMISE_DEBUG
+#define PROMISE_ASSERT MOZ_RELEASE_ASSERT
+#else
+#define PROMISE_ASSERT(...) do { } while (0)
+#endif
+
 namespace mozilla {
 
 extern LazyLogModule gMozPromiseLog;
 
 #define PROMISE_LOG(x, ...) \
   MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__))
 
 namespace detail {
@@ -115,16 +125,18 @@ protected:
   virtual ~MozPromiseRefcountable() {}
 };
 
 template<typename T> class MozPromiseHolder;
 template<typename T> class MozPromiseRequestHolder;
 template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
 class MozPromise : public MozPromiseRefcountable
 {
+  static const uint32_t sMagic = 0xcecace11;
+
 public:
   typedef ResolveValueT ResolveValueType;
   typedef RejectValueT RejectValueType;
   class ResolveOrRejectValue
   {
   public:
     template<typename ResolveValueType_>
     void SetResolve(ResolveValueType_&& aResolveValue)
@@ -171,16 +183,19 @@ public:
 protected:
   // MozPromise is the public type, and never constructed directly. Construct
   // a MozPromise::Private, defined below.
   MozPromise(const char* aCreationSite, bool aIsCompletionPromise)
     : mCreationSite(aCreationSite)
     , mMutex("MozPromise Mutex")
     , mHaveRequest(false)
     , mIsCompletionPromise(aIsCompletionPromise)
+#ifdef PROMISE_DEBUG
+    , mMagic4(mMutex.mLock)
+#endif
   {
     PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite, this);
   }
 
 public:
   // MozPromise::Private allows us to separate the public interface (upon which
   // consumers of the promise may invoke methods like Then()) from the private
   // interface (upon which the creator of the promise may invoke Resolve() or
@@ -273,21 +288,16 @@ public:
     }
     return holder->Promise();
   }
 
   class Request : public MozPromiseRefcountable
   {
   public:
     virtual void Disconnect() = 0;
-
-    // MSVC complains when an inner class (ThenValueBase::{Resolve,Reject}Runnable)
-    // tries to access an inherited protected member.
-    bool IsDisconnected() const { return mDisconnected; }
-
     virtual void AssertIsDead() = 0;
 
   protected:
     Request() : mComplete(false), mDisconnected(false) {}
     virtual ~Request() {}
 
     bool mComplete;
     bool mDisconnected;
@@ -299,16 +309,17 @@ protected:
    * A ThenValue tracks a single consumer waiting on the promise. When a consumer
    * invokes promise->Then(...), a ThenValue is created. Once the Promise is
    * resolved or rejected, a {Resolve,Reject}Runnable is dispatched, which
    * invokes the resolve/reject method and then deletes the ThenValue.
    */
   class ThenValueBase : public Request
   {
     friend class MozPromise;
+    static const uint32_t sMagic = 0xfadece11;
 
   public:
     class ResolveOrRejectRunnable : public Runnable
     {
     public:
       ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise)
         : mThenValue(aThenValue)
         , mPromise(aPromise)
@@ -338,68 +349,79 @@ protected:
     };
 
     ThenValueBase(AbstractThread* aResponseTarget,
                   const char* aCallSite)
       : mResponseTarget(aResponseTarget)
       , mCallSite(aCallSite)
     { }
 
+#ifdef PROMISE_DEBUG
+    ~ThenValueBase()
+    {
+      mMagic1 = 0;
+      mMagic2 = 0;
+    }
+#endif
+
     void AssertIsDead() override
     {
+      PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
       // We want to assert that this ThenValues is dead - that is to say, that
       // there are no consumers waiting for the result. In the case of a normal
       // ThenValue, we check that it has been disconnected, which is the way
       // that the consumer signals that it no longer wishes to hear about the
       // result. If this ThenValue has a completion promise (which is mutually
       // exclusive with being disconnectable), we recursively assert that every
       // ThenValue associated with the completion promise is dead.
       if (mCompletionPromise) {
         mCompletionPromise->AssertIsDead();
       } else {
         MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected);
       }
     }
 
     void Dispatch(MozPromise *aPromise)
     {
+      PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
       aPromise->mMutex.AssertCurrentThreadOwns();
       MOZ_ASSERT(!aPromise->IsPending());
 
-      RefPtr<Runnable> runnable =
-        static_cast<Runnable*>(new (typename ThenValueBase::ResolveOrRejectRunnable)(this, aPromise));
+      nsCOMPtr<nsIRunnable> r = new ResolveOrRejectRunnable(this, aPromise);
       PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]",
-                  aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", ThenValueBase::mCallSite,
-                  runnable.get(), aPromise, this);
+                  aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", mCallSite,
+                  r.get(), aPromise, this);
 
       // Promise consumers are allowed to disconnect the Request object and
       // then shut down the thread or task queue that the promise result would
       // be dispatched on. So we unfortunately can't assert that promise
       // dispatch succeeds. :-(
-      mResponseTarget->Dispatch(runnable.forget(), AbstractThread::DontAssertDispatchSuccess);
+      mResponseTarget->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
     }
 
     void Disconnect() override
     {
-      MOZ_ASSERT(ThenValueBase::mResponseTarget->IsCurrentThreadIn());
+      MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
       MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete);
       Request::mDisconnected = true;
 
       // We could support rejecting the completion promise on disconnection, but
       // then we'd need to have some sort of default reject value. The use cases
       // of disconnection and completion promise chaining seem pretty orthogonal,
       // so let's use assert against it.
       MOZ_DIAGNOSTIC_ASSERT(!mCompletionPromise);
     }
 
   protected:
     virtual already_AddRefed<MozPromise> DoResolveOrRejectInternal(const ResolveOrRejectValue& aValue) = 0;
 
     void DoResolveOrReject(const ResolveOrRejectValue& aValue)
     {
+      PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
+      MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
       Request::mComplete = true;
       if (Request::mDisconnected) {
         PROMISE_LOG("ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", this);
         return;
       }
 
       // Invoke the resolve or reject method.
       RefPtr<MozPromise> result = DoResolveOrRejectInternal(aValue);
@@ -411,17 +433,23 @@ protected:
           result->ChainTo(p.forget(), "<chained completion promise>");
         } else {
           p->ResolveOrReject(aValue, "<completion of non-promise-returning method>");
         }
       }
     }
 
     RefPtr<AbstractThread> mResponseTarget; // May be released on any thread.
+#ifdef PROMISE_DEBUG
+    uint32_t mMagic1 = sMagic;
+#endif
     RefPtr<Private> mCompletionPromise;
+#ifdef PROMISE_DEBUG
+    uint32_t mMagic2 = sMagic;
+#endif
     const char* mCallSite;
   };
 
   /*
    * We create two overloads for invoking Resolve/Reject Methods so as to
    * make the resolve/reject value argument "optional".
    */
 
@@ -661,16 +689,17 @@ protected:
   private:
     Maybe<ResolveRejectFunction> mResolveRejectFunction; // Only accessed and deleted on dispatch thread.
   };
 
 public:
   void ThenInternal(AbstractThread* aResponseThread, ThenValueBase* aThenValue,
                     const char* aCallSite)
   {
+    PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
     MutexAutoLock lock(mMutex);
     MOZ_ASSERT(aResponseThread->IsDispatchReliable());
     MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest);
     mHaveRequest = true;
     PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]",
                 aCallSite, this, aThenValue, (int) IsPending());
     if (!IsPending()) {
       aThenValue->Dispatch(this);
@@ -814,16 +843,17 @@ public:
   }
 
   // Note we expose the function AssertIsDead() instead of IsDead() since
   // checking IsDead() is a data race in the situation where the request is not
   // dead. Therefore we enforce the form |Assert(IsDead())| by exposing
   // AssertIsDead() only.
   void AssertIsDead()
   {
+    PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
     MutexAutoLock lock(mMutex);
     for (auto&& then : mThenValues) {
       then->AssertIsDead();
     }
     for (auto&& chained : mChainedPromises) {
       chained->AssertIsDead();
     }
   }
@@ -868,58 +898,79 @@ protected:
     AssertIsDead();
     // We can't guarantee a completion promise will always be revolved or
     // rejected since ResolveOrRejectRunnable might not run when dispatch fails.
     if (!mIsCompletionPromise) {
       MOZ_ASSERT(!IsPending());
       MOZ_ASSERT(mThenValues.IsEmpty());
       MOZ_ASSERT(mChainedPromises.IsEmpty());
     }
+#ifdef PROMISE_DEBUG
+    mMagic1 = 0;
+    mMagic2 = 0;
+    mMagic3 = 0;
+    mMagic4 = nullptr;
+#endif
   };
 
   const char* mCreationSite; // For logging
   Mutex mMutex;
   ResolveOrRejectValue mValue;
+#ifdef PROMISE_DEBUG
+  uint32_t mMagic1 = sMagic;
+#endif
   nsTArray<RefPtr<ThenValueBase>> mThenValues;
+#ifdef PROMISE_DEBUG
+  uint32_t mMagic2 = sMagic;
+#endif
   nsTArray<RefPtr<Private>> mChainedPromises;
+#ifdef PROMISE_DEBUG
+  uint32_t mMagic3 = sMagic;
+#endif
   bool mHaveRequest;
   const bool mIsCompletionPromise;
+#ifdef PROMISE_DEBUG
+  void* mMagic4;
+#endif
 };
 
 template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
 class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private
   : public MozPromise<ResolveValueT, RejectValueT, IsExclusive>
 {
 public:
   explicit Private(const char* aCreationSite, bool aIsCompletionPromise = false)
     : MozPromise(aCreationSite, aIsCompletionPromise) {}
 
   template<typename ResolveValueT_>
   void Resolve(ResolveValueT_&& aResolveValue, const char* aResolveSite)
   {
+    PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
     MutexAutoLock lock(mMutex);
     MOZ_ASSERT(IsPending());
     PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite, this, mCreationSite);
     mValue.SetResolve(Forward<ResolveValueT_>(aResolveValue));
     DispatchAll();
   }
 
   template<typename RejectValueT_>
   void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite)
   {
+    PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
     MutexAutoLock lock(mMutex);
     MOZ_ASSERT(IsPending());
     PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this, mCreationSite);
     mValue.SetReject(Forward<RejectValueT_>(aRejectValue));
     DispatchAll();
   }
 
   template<typename ResolveOrRejectValue_>
   void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite)
   {
+    PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
     MutexAutoLock lock(mMutex);
     MOZ_ASSERT(IsPending());
     PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite, this, mCreationSite);
     mValue = Forward<ResolveOrRejectValue_>(aValue);
     DispatchAll();
   }
 };
 
@@ -1339,12 +1390,14 @@ InvokeAsync(AbstractThread* aTarget, con
                 "Function object must not be passed by lvalue-ref (to avoid "
                 "unplanned copies); Consider move()ing the object.");
   return detail::InvokeAsync(aTarget, aCallerName,
                              detail::AllowInvokeAsyncFunctionLVRef(),
                              Forward<Function>(aFunction));
 }
 
 #undef PROMISE_LOG
+#undef PROMISE_ASSERT
+#undef PROMISE_DEBUG
 
 } // namespace mozilla
 
 #endif
--- a/xpcom/threads/Mutex.h
+++ b/xpcom/threads/Mutex.h
@@ -105,16 +105,20 @@ public:
 private:
   OffTheBooksMutex();
   OffTheBooksMutex(const OffTheBooksMutex&);
   OffTheBooksMutex& operator=(const OffTheBooksMutex&);
 
   PRLock* mLock;
 
   friend class CondVar;
+
+  // MozPromise needs to access mLock for debugging purpose.
+  template<typename, typename, bool>
+  friend class MozPromise;
 };
 
 /**
  * Mutex
  * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this
  * mutex within a scope, instead of calling Lock/Unlock directly.
  */
 class Mutex : public OffTheBooksMutex