Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 16 Feb 2017 09:26:25 -0800
changeset 343270 4158b1d8bb2ab89048c8be560b35fcfc12726a84
parent 343252 25929185c46777cf4e9eb3e9aad2f8a8a227178f (current diff)
parent 343269 ab0a5b797f02b569bce706e7d6c08f99729687f9 (diff)
child 343284 bf6b9caab2c7eb3ebc642afd82bc19598829c6f3
push id31374
push userkwierso@gmail.com
push dateThu, 16 Feb 2017 17:26:30 +0000
treeherdermozilla-central@4158b1d8bb2a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone54.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 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