Merge mozilla-central to inbound. a=merge CLOSED TREE
authorNoemi Erli <nerli@mozilla.com>
Tue, 09 Oct 2018 00:57:11 +0300
changeset 495860 26913fab74050dbe195b330a250795c14407bf4f
parent 495852 2b75b760c83ed6292aa24e418711ef0d8c5c5137 (current diff)
parent 495744 df860e79a6a3cc41712cfd86ffbd18cf84fce626 (diff)
child 495861 70b36ec329e7ff9139c098f0a029243a57df61be
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js
devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js
devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js
devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js
devtools/server/actors/promises.js
devtools/server/tests/unit/test_promises_actor_attach.js
devtools/server/tests/unit/test_promises_actor_exist.js
devtools/server/tests/unit/test_promises_actor_list_promises.js
devtools/server/tests/unit/test_promises_actor_onnewpromise.js
devtools/server/tests/unit/test_promises_actor_onpromisesettled.js
devtools/server/tests/unit/test_promises_client_getdependentpromises.js
devtools/server/tests/unit/test_promises_object_creationtimestamp.js
devtools/server/tests/unit/test_promises_object_timetosettle-01.js
devtools/server/tests/unit/test_promises_object_timetosettle-02.js
devtools/shared/fronts/promises.js
devtools/shared/specs/promises.js
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -75,22 +75,16 @@
     <popupnotification id="addon-webext-permissions-notification" hidden="true">
       <popupnotificationcontent class="addon-webext-perm-notification-content" orient="vertical">
         <description id="addon-webext-perm-text" class="addon-webext-perm-text"/>
         <label id="addon-webext-perm-intro" class="addon-webext-perm-text"/>
         <html:ul id="addon-webext-perm-list" class="addon-webext-perm-list"/>
       </popupnotificationcontent>
     </popupnotification>
 
-    <popupnotification id="addon-installed-notification" hidden="true">
-      <popupnotificationcontent class="addon-installed-notification-content" orient="vertical">
-        <description>&addonPostInstallMessage.label;</description>
-      </popupnotificationcontent>
-    </popupnotification>
-
     <popupnotification id="contextual-feature-recommendation-notification" hidden="true">
       <popupnotificationheader id="cfr-notification-header">
         <stack id="cfr-notification-header-stack">
           <description id="cfr-notification-header-label"></description>
           <label id="cfr-notification-header-link" class="text-link">
             <xul:image id="cfr-notification-header-image"/>
           </label>
         </stack>
--- a/browser/base/content/test/webextensions/head.js
+++ b/browser/base/content/test/webextensions/head.js
@@ -34,16 +34,37 @@ function promisePopupNotificationShown(n
       PopupNotifications.panel.removeEventListener("popupshown", popupshown);
       resolve(PopupNotifications.panel.firstElementChild);
     }
 
     PopupNotifications.panel.addEventListener("popupshown", popupshown);
   });
 }
 
+function promiseAppMenuNotificationShown(id) {
+  ChromeUtils.import("resource://gre/modules/AppMenuNotifications.jsm");
+  return new Promise(resolve => {
+    function popupshown() {
+      let notification = AppMenuNotifications.activeNotification;
+      if (!notification) { return; }
+
+      is(notification.id, id, `${id} notification shown`);
+      ok(PanelUI.isNotificationPanelOpen, "notification panel open");
+
+      PanelUI.notificationPanel.removeEventListener("popupshown", popupshown);
+
+      let popupnotificationID = PanelUI._getPopupId(notification);
+      let popupnotification = document.getElementById(popupnotificationID);
+
+      resolve(popupnotification);
+    }
+    PanelUI.notificationPanel.addEventListener("popupshown", popupshown);
+  });
+}
+
 /**
  * Wait for a specific install event to fire for a given addon
  *
  * @param {AddonWrapper} addon
  *        The addon to watch for an event on
  * @param {string}
  *        The name of the event to watch for (e.g., onInstallEnded)
  *
@@ -302,17 +323,17 @@ async function testInstallMethod(install
 
     if (cancel) {
       panel.secondaryButton.click();
       try {
         await installMethodPromise;
       } catch (err) {}
     } else {
       // Look for post-install notification
-      let postInstallPromise = promisePopupNotificationShown("addon-installed");
+      let postInstallPromise = promiseAppMenuNotificationShown("addon-installed");
       panel.button.click();
 
       // Press OK on the post-install notification
       panel = await postInstallPromise;
       panel.button.click();
 
       await installMethodPromise;
     }
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -142,16 +142,29 @@
                      dropmarkerhidden="true"
                      checkboxhidden="true"
                      buttonhighlight="true"
                      hidden="true">
     <popupnotificationcontent id="update-restart-notification-content" orient="vertical">
       <description id="update-restart-description">&updateRestart.message2;</description>
     </popupnotificationcontent>
   </popupnotification>
+
+  <popupnotification id="appMenu-addon-installed-notification"
+                     popupid="addon-installed"
+                     closebuttonhidden="true"
+                     secondarybuttonhidden="true"
+                     dropmarkerhidden="true"
+                     checkboxhidden="true"
+                     buttonhighlight="true"
+                     hidden="true">
+    <popupnotificationcontent class="addon-installed-notification-content" orient="vertical">
+      <description>&addonPostInstallMessage.label;</description>
+    </popupnotificationcontent>
+  </popupnotification>
 </panel>
 
 <menupopup id="customizationPaletteItemContextMenu"
            onpopupshowing="gCustomizeMode.onPaletteContextMenuShowing(event)">
   <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
             class="customize-context-addToToolbar"
             accesskey="&customizeMenu.addToToolbar.accesskey;"
             label="&customizeMenu.addToToolbar.label;"/>
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -748,27 +748,46 @@ const PanelUI = {
   },
 
   _clearAllNotifications() {
     this._clearNotificationPanel();
     this._clearBadge();
     this._clearBannerItem();
   },
 
+  _formatDescriptionMessage(n) {
+    let text = {};
+    let array = n.options.message.split("<>");
+    text.start = array[0] || "";
+    text.name = n.options.name || "";
+    text.end = array[1] || "";
+    return text;
+  },
+
   _refreshNotificationPanel(notification) {
     this._clearNotificationPanel();
 
     let popupnotificationID = this._getPopupId(notification);
     let popupnotification = document.getElementById(popupnotificationID);
 
     popupnotification.setAttribute("id", popupnotificationID);
     popupnotification.setAttribute("buttoncommand", "PanelUI._onNotificationButtonEvent(event, 'buttoncommand');");
     popupnotification.setAttribute("secondarybuttoncommand",
       "PanelUI._onNotificationButtonEvent(event, 'secondarybuttoncommand');");
 
+    if (notification.options.message) {
+      let desc = this._formatDescriptionMessage(notification);
+      popupnotification.setAttribute("label", desc.start);
+      popupnotification.setAttribute("name", desc.name);
+      popupnotification.setAttribute("endlabel", desc.end);
+    }
+    if (notification.options.popupIconURL) {
+      popupnotification.setAttribute("icon", notification.options.popupIconURL);
+    }
+
     popupnotification.notification = notification;
     popupnotification.hidden = false;
   },
 
   _showBadge(notification) {
     let badgeStatus = this._getBadgeStatus(notification);
     this.menuButton.setAttribute("badge-status", badgeStatus);
   },
--- a/browser/components/resistfingerprinting/test/mochitest/test_bug863246_resource_uri.html
+++ b/browser/components/resistfingerprinting/test/mochitest/test_bug863246_resource_uri.html
@@ -1,21 +1,15 @@
 <!DOCTYPE html>
 <meta charset="utf8">
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <script src="/tests/SimpleTest/AddTask.js"></script>
 <script>
 /* global SimpleTest SpecialPowers add_task */
 
-function waitForDOMContentLoaded() {
-  return new Promise((aResolve) => {
-    document.addEventListener("DOMContentLoaded", aResolve);
-  });
-}
-
 function testResourceUri(aTest, aUri, aContentAccessible) {
   return new Promise((aResolve) => {
     let link = document.createElement("link");
     link.rel = "stylesheet";
     link.onload = () => {
       SimpleTest.ok(aContentAccessible, aTest);
       aResolve();
     };
@@ -24,17 +18,16 @@ function testResourceUri(aTest, aUri, aC
       aResolve();
     };
     link.href = aUri;
     document.head.appendChild(link);
   });
 }
 
 add_task(async function() {
-  await waitForDOMContentLoaded();
   await testResourceUri(
       "resource://content-accessible is content-accessible",
       "resource://content-accessible/viewsource.css",
       true);
   await testResourceUri(
       "resource://gre-resources is not content-accessible",
       "resource://gre-resources/html.css",
       false);
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3583,25 +3583,30 @@ var SessionStoreInternal = {
     Services.obs.notifyObservers(aWindow, NOTIFY_SINGLE_WINDOW_RESTORED);
 
     this._sendRestoreCompletedNotifications();
   },
 
   /**
    * Prepare connection to host beforehand.
    *
+   * @param tab
+   *        Tab we are loading from.
    * @param url
    *        URL of a host.
    * @returns a flag indicates whether a connection has been made
    */
-  prepareConnectionToHost(url) {
+  prepareConnectionToHost(tab, url) {
     if (!url.startsWith("about:")) {
+      let principal = Services.scriptSecurityManager.createNullPrincipal({
+        userContextId: tab.userContextId,
+      });
       let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect);
       let uri = Services.io.newURI(url);
-      sc.speculativeConnect(uri, null, null);
+      sc.speculativeConnect2(uri, principal, null);
       return true;
     }
     return false;
   },
 
   /**
    * Make a connection to a host when users hover mouse on a tab.
    * This will also set a flag in the tab to prevent us from speculatively
@@ -3609,17 +3614,17 @@ var SessionStoreInternal = {
    *
    * @param tab
    *        a tab to speculatively connect on mouse hover.
    */
   speculativeConnectOnTabHover(tab) {
     let tabState = TAB_LAZY_STATES.get(tab);
     if (tabState && !tabState.connectionPrepared) {
       let url = this.getLazyTabValue(tab, "url");
-      let prepared = this.prepareConnectionToHost(url);
+      let prepared = this.prepareConnectionToHost(tab, url);
       // This is used to test if a connection has been made beforehand.
       if (gDebuggingEnabled) {
         tab.__test_connection_prepared = prepared;
         tab.__test_connection_url = url;
       }
       // A flag indicate that we've prepared a connection for this tab and
       // if is called again, we shouldn't prepare another connection.
       tabState.connectionPrepared = true;
@@ -3916,17 +3921,17 @@ var SessionStoreInternal = {
       } else if (!forceOnDemand) {
         TabRestoreQueue.add(tab);
         // Check if a tab is in queue and will be restored
         // after the currently loading tabs. If so, prepare
         // a connection to host to speed up page loading.
         if (TabRestoreQueue.willRestoreSoon(tab)) {
           if (activeIndex in tabData.entries) {
             let url = tabData.entries[activeIndex].url;
-            let prepared = this.prepareConnectionToHost(url);
+            let prepared = this.prepareConnectionToHost(tab, url);
             if (gDebuggingEnabled) {
               tab.__test_connection_prepared = prepared;
               tab.__test_connection_url = url;
             }
           }
         }
         this.restoreNextTab();
       }
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -396,18 +396,17 @@ var ExtensionsUI = {
       let {browser, window} = getTabBrowser(target);
       window.PopupNotifications.show(browser, "addon-webext-defaultsearch", strings.text,
                                      "addons-notification-icon", action,
                                      secondaryActions, popupOptions);
     });
   },
 
   showInstallNotification(target, addon) {
-    let {browser, window} = getTabBrowser(target);
-    let popups = window.PopupNotifications;
+    let {window} = getTabBrowser(target);
 
     let brandBundle = window.document.getElementById("bundle_brand");
     let appName = brandBundle.getString("brandShortName");
     let bundle = window.gNavigatorBundle;
 
     let message = bundle.getFormattedString("addonPostInstall.message1",
                                             ["<>", appName]);
     return new Promise(resolve => {
@@ -416,26 +415,20 @@ var ExtensionsUI = {
         accessKey: bundle.getString("addonPostInstall.okay.key"),
         callback: resolve,
       };
 
       let icon = addon.isWebExtension ?
                  AddonManager.getPreferredIconURL(addon, 32, window) || DEFAULT_EXTENSION_ICON :
                  "chrome://browser/skin/addons/addon-install-installed.svg";
       let options = {
-        hideClose: true,
-        timeout: Date.now() + 30000,
+        name: addon.name,
+        message,
         popupIconURL: icon,
-        eventCallback(topic) {
-          if (topic == "dismissed") {
-            resolve();
-          }
-        },
-        name: addon.name,
+        onDismissed: resolve,
       };
 
-      popups.show(browser, "addon-installed", message, "addons-notification-icon",
-                  action, null, options);
+      AppMenuNotifications.showNotification("addon-installed", action, null, options);
     });
   },
 };
 
 EventEmitter.decorate(ExtensionsUI);
--- a/devtools/client/inspector/boxmodel/box-model.js
+++ b/devtools/client/inspector/boxmodel/box-model.js
@@ -176,17 +176,22 @@ BoxModel.prototype = {
       }
 
       this.inspector.emit("boxmodel-view-updated", this._updateReasons);
 
       this._lastRequest = null;
       this._updateReasons = [];
 
       return null;
-    }).bind(this))().catch(console.error);
+    }).bind(this))().catch(error => {
+      // If we failed because we were being destroyed while waiting for a request, ignore.
+      if (this.document) {
+        console.error(error);
+      }
+    });
 
     this._lastRequest = lastRequest;
   },
 
   /**
    * Hides the box-model highlighter on the currently selected element.
    */
   onHideBoxModelHighlighter() {
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
@@ -0,0 +1,147 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { colorUtils } = require("devtools/shared/css/color");
+
+const Types = require("../types");
+
+class FlexItemSizingOutline extends PureComponent {
+  static get propTypes() {
+    return {
+      color: PropTypes.string.isRequired,
+      flexDirection: PropTypes.string.isRequired,
+      flexItem: PropTypes.shape(Types.flexItem).isRequired,
+    };
+  }
+
+  renderBasisOutline(mainBaseSize) {
+    return (
+      dom.div({
+        className: "flex-outline-basis" + (!mainBaseSize ? " zero-basis" : ""),
+        style: {
+          color: colorUtils.setAlpha(this.props.color, 0.4),
+        },
+      })
+    );
+  }
+
+  renderDeltaOutline(mainDeltaSize) {
+    if (!mainDeltaSize) {
+      return null;
+    }
+
+    return (
+      dom.div({
+        className: "flex-outline-delta",
+        style: {
+          backgroundColor: colorUtils.setAlpha(this.props.color, 0.1)
+        }
+      })
+    );
+  }
+
+  renderFinalOutline(mainFinalSize, mainMaxSize, mainMinSize) {
+    const isClamped = mainFinalSize === mainMaxSize ||
+                      mainFinalSize === mainMinSize;
+
+    return (
+      dom.div({
+        className: "flex-outline-final" + (isClamped ? " clamped" : "")
+      })
+    );
+  }
+
+  renderPoint(name) {
+    return dom.div({ className: `flex-outline-point ${name}`, "data-label": name });
+  }
+
+  render() {
+    const {
+      mainBaseSize,
+      mainDeltaSize,
+      mainMaxSize,
+      mainMinSize,
+    } = this.props.flexItem.flexItemSizing;
+    const isRow = this.props.flexDirection.startsWith("row");
+
+    // Calculate the final size. This is base + delta, then clamped by min or max.
+    let mainFinalSize = mainBaseSize + mainDeltaSize;
+    mainFinalSize = Math.max(mainFinalSize, mainMinSize);
+    mainFinalSize = Math.min(mainFinalSize, mainMaxSize);
+
+    // The max size is only interesting to show if it did clamp the item
+    const showMax = mainMaxSize === mainFinalSize;
+
+    // The min size is only really interesting if it actually clamped the item.
+    const showMin = mainMinSize === mainFinalSize;
+
+    // Sort all of the dimensions in order to come up with a grid track template.
+    // Make mainDeltaSize start from the same point as the other ones so we can compare.
+    let sizes = [
+      { name: "basis-end", size: mainBaseSize },
+      { name: "final-end", size: mainFinalSize }
+    ];
+
+    if (mainDeltaSize > 0) {
+      sizes.push({ name: "delta-start", size: mainBaseSize });
+      sizes.push({ name: "delta-end", size: mainBaseSize + mainDeltaSize });
+    } else {
+      sizes.push({ name: "delta-start", size: mainBaseSize + mainDeltaSize });
+      sizes.push({ name: "delta-end", size: mainBaseSize });
+    }
+
+    if (showMax) {
+      sizes.push({ name: "max", size: mainMaxSize });
+    }
+    if (showMin) {
+      sizes.push({ name: "min", size: mainMinSize });
+    }
+
+    sizes = sizes.sort((a, b) => a.size - b.size);
+
+    let gridTemplateColumns = "[final-start basis-start";
+    let accumulatedSize = 0;
+    for (const { name, size } of sizes) {
+      const breadth = Math.round(size - accumulatedSize);
+      if (breadth === 0) {
+        gridTemplateColumns += ` ${name}`;
+        continue;
+      }
+
+      gridTemplateColumns += `] ${breadth}fr [${name}`;
+      accumulatedSize = size;
+    }
+    gridTemplateColumns += "]";
+
+    return (
+      dom.div({ className: "flex-outline-container" },
+        dom.div(
+          {
+            className: "flex-outline" +
+                       (isRow ? " row" : " column") +
+                       (mainDeltaSize > 0 ? " growing" : " shrinking"),
+            style: {
+              color: this.props.color,
+              gridTemplateColumns
+            }
+          },
+          this.renderPoint("basis"),
+          this.renderPoint("final"),
+          showMin ? this.renderPoint("min") : null,
+          showMax ? this.renderPoint("max") : null,
+          this.renderBasisOutline(mainBaseSize),
+          this.renderDeltaOutline(mainDeltaSize),
+          this.renderFinalOutline(mainFinalSize, mainMaxSize, mainMinSize)
+        )
+      )
+    );
+  }
+}
+
+module.exports = FlexItemSizingOutline;
--- a/devtools/client/inspector/flexbox/components/Flexbox.js
+++ b/devtools/client/inspector/flexbox/components/Flexbox.js
@@ -1,37 +1,46 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const {
+  createElement,
+  createFactory,
+  Fragment,
+  PureComponent,
+} = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { getStr } = require("devtools/client/inspector/layout/utils/l10n");
 
 loader.lazyGetter(this, "FlexContainerProperties", function() {
   return createFactory(require("./FlexContainerProperties"));
 });
 loader.lazyGetter(this, "FlexItemList", function() {
   return createFactory(require("./FlexItemList"));
 });
+loader.lazyGetter(this, "FlexItemSizingOutline", function() {
+  return createFactory(require("./FlexItemSizingOutline"));
+});
 loader.lazyGetter(this, "FlexItemSizingProperties", function() {
   return createFactory(require("./FlexItemSizingProperties"));
 });
 loader.lazyGetter(this, "Header", function() {
   return createFactory(require("./Header"));
 });
 
 const Types = require("../types");
 
 class Flexbox extends PureComponent {
   static get propTypes() {
     return {
+      flexbox: PropTypes.shape(Types.flexbox).isRequired,
       flexContainer: PropTypes.shape(Types.flexContainer).isRequired,
       getSwatchColorPickerTooltip: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onSetFlexboxOverlayColor: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       onToggleFlexboxHighlighter: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
     };
@@ -56,30 +65,40 @@ class Flexbox extends PureComponent {
     return FlexItemList({
       flexItems,
       setSelectedNode,
     });
   }
 
   renderFlexItemSizing() {
     const {
+      color,
+    } = this.props.flexbox;
+    const {
       flexItems,
       flexItemShown,
       properties,
     } = this.props.flexContainer;
 
     const flexItem = flexItems.find(item => item.nodeFront.actorID === flexItemShown);
     if (!flexItem) {
       return null;
     }
 
-    return FlexItemSizingProperties({
-      flexDirection: properties["flex-direction"],
-      flexItem,
-    });
+    return createElement(Fragment, null,
+      FlexItemSizingOutline({
+        color,
+        flexDirection: properties["flex-direction"],
+        flexItem,
+      }),
+      FlexItemSizingProperties({
+        flexDirection: properties["flex-direction"],
+        flexItem,
+      })
+    );
   }
 
   render() {
     const {
       flexContainer,
       getSwatchColorPickerTooltip,
       onHideBoxModelHighlighter,
       onSetFlexboxOverlayColor,
--- a/devtools/client/inspector/flexbox/components/moz.build
+++ b/devtools/client/inspector/flexbox/components/moz.build
@@ -6,11 +6,12 @@
 
 DevToolsModules(
     'Flexbox.js',
     'FlexContainer.js',
     'FlexContainerProperties.js',
     'FlexItem.js',
     'FlexItemList.js',
     'FlexItemSelector.js',
+    'FlexItemSizingOutline.js',
     'FlexItemSizingProperties.js',
     'Header.js',
 )
--- a/devtools/client/inspector/flexbox/moz.build
+++ b/devtools/client/inspector/flexbox/moz.build
@@ -9,8 +9,10 @@ DIRS += [
     'components',
     'reducers',
 ]
 
 DevToolsModules(
     'flexbox.js',
     'types.js',
 )
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../../../.eslintrc.mochitests.js"
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  doc_flexbox_simple.html
+  head.js
+  !/devtools/client/inspector/test/head.js
+  !/devtools/client/inspector/test/shared-head.js
+  !/devtools/client/shared/test/shared-head.js
+
+[browser_flexbox_item_outline_exists.js]
+[browser_flexbox_item_outline_rotates_for_column.js]
+[browser_flexbox_item_outline_has_correct_layout.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_exists.js
@@ -0,0 +1,34 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item outline exists when a flex item is selected.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_simple.html";
+
+add_task(async function() {
+  await addTab(TEST_URI);
+  const { inspector, flexboxInspector } = await openLayoutView();
+  const { document: doc } = flexboxInspector;
+
+  // Select a flex item in the test document and wait for the outline to be rendered.
+  const onFlexItemOutlineRendered = waitForDOM(doc, ".flex-outline-container");
+  await selectNode(".item", inspector);
+  const [flexOutlineContainer] = await onFlexItemOutlineRendered;
+
+  ok(flexOutlineContainer, "The flex outline exists in the DOM");
+
+  const [basis, final] = [...flexOutlineContainer.querySelectorAll(
+    ".flex-outline-basis, .flex-outline-final")];
+
+  ok(basis, "The basis outline exists");
+  ok(final, "The final outline exists");
+
+  const [basisPoint, finalPoint] = [...flexOutlineContainer.querySelectorAll(
+    ".flex-outline-point.basis, .flex-outline-point.final")];
+
+  ok(basisPoint, "The basis point exists");
+  ok(finalPoint, "The final point exists");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_has_correct_layout.js
@@ -0,0 +1,52 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item outline has a correct layout. This outline is built using css
+// grid under the hood to position everything. So we want to check that the template for
+// this grid has been correctly generated depending on the item that is currently
+// selected.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_simple.html";
+
+const TEST_DATA = [{
+  selector: ".shrinking .item",
+  expectedGridTemplate: "[final-start basis-start] 300fr [final-end delta-start] " +
+                        "200fr [basis-end delta-end]"
+}, {
+  selector: ".shrinking.is-clamped .item",
+  expectedGridTemplate: "[final-start basis-start] 300fr [delta-start] " +
+                        "50fr [final-end min] 150fr [basis-end delta-end]"
+}, {
+  selector: ".growing .item",
+  expectedGridTemplate: "[final-start basis-start] 200fr [basis-end delta-start] " +
+                        "100fr [final-end delta-end]"
+}, {
+  selector: ".growing.is-clamped .item",
+  expectedGridTemplate: "[final-start basis-start] 200fr [basis-end delta-start] " +
+                        "50fr [final-end max] 50fr [delta-end]"
+}];
+
+add_task(async function() {
+  await addTab(TEST_URI);
+  const { inspector, flexboxInspector } = await openLayoutView();
+  const { document: doc } = flexboxInspector;
+
+  for (const {selector, expectedGridTemplate} of TEST_DATA) {
+    info(`Checking the grid template for the flex item outline for ${selector}`);
+
+    const flexOutline = await selectNodeAndGetFlexOutline(selector, inspector, doc);
+
+    is(flexOutline.style.gridTemplateColumns, expectedGridTemplate,
+       "Grid template is correct");
+  }
+});
+
+async function selectNodeAndGetFlexOutline(selector, inspector, doc) {
+  const onFlexItemOutlineRendered = waitForDOM(doc, ".flex-outline");
+  await selectNode(selector, inspector);
+  const [flexOutlineContainer] = await onFlexItemOutlineRendered;
+  return flexOutlineContainer;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_rotates_for_column.js
@@ -0,0 +1,39 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item outline is rotated for flex items in a column flexbox layout.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_simple.html";
+
+add_task(async function() {
+  await addTab(TEST_URI);
+  const { inspector, flexboxInspector } = await openLayoutView();
+  const { document: doc } = flexboxInspector;
+
+  // Select a flex item in the row flexbox layout.
+  let onFlexItemOutlineRendered = waitForDOM(doc,
+    ".flex-outline-container .flex-outline");
+  await selectNode(".container .item", inspector);
+  let [flexOutline] = await onFlexItemOutlineRendered;
+
+  ok(flexOutline.classList.contains("row"), "The flex outline has the row class");
+
+  // Check that the outline is wider than it is tall in the configuration.
+  let bounds = flexOutline.getBoxQuads()[0].getBounds();
+  ok(bounds.width > bounds.height, "The outline looks like a row");
+
+  // Select a flex item in the column flexbox layout.
+  onFlexItemOutlineRendered = waitForDOM(doc,
+    ".flex-outline-container .flex-outline");
+  await selectNode(".container.column .item", inspector);
+  ([flexOutline] = await onFlexItemOutlineRendered);
+
+  ok(flexOutline.classList.contains("column"), "The flex outline has the column class");
+
+  // Check that the outline is taller than it is wide in the configuration.
+  bounds = flexOutline.getBoxQuads()[0].getBounds();
+  ok(bounds.height > bounds.width, "The outline looks like a column");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/doc_flexbox_simple.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+.container {
+  width: 300px;
+  height: 150px;
+  margin: 10px;
+  display: flex;
+}
+.container.column {
+  height: 300px;
+  width: 150px;
+  flex-direction: column;
+}
+.item {
+  background: #0004;
+}
+.shrinking .item {
+  flex-basis: 500px;
+  flex-shrink: 1;
+}
+.shrinking.is-clamped .item {
+  min-width: 350px;
+}
+.growing .item {
+  flex-basis: 200px;
+  flex-grow: 1;
+}
+.growing.is-clamped .item {
+  max-width: 250px;
+}
+</style>
+<div class="container">
+  <div class="item">flex item in a row flex container</div>
+</div>
+<div class="container column">
+  <div class="item">flex item in a column flex container</div>
+</div>
+<div class="container shrinking">
+  <div class="item">Shrinking flex item</div>
+</div>
+<div class="container shrinking is-clamped">
+  <div class="item">Shrinking and clamped flex item</div>
+</div>
+<div class="container growing">
+  <div class="item">Growing flex item</div>
+</div>
+<div class="container growing is-clamped">
+  <div class="item">Growing and clamped flex item</div>
+</div>
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/head.js
@@ -0,0 +1,21 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../test/head.js */
+"use strict";
+
+// Import the inspector's head.js first (which itself imports shared-head.js).
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
+  this);
+
+// Make sure only the flexbox layout accordion is opened, and the others are closed.
+Services.prefs.setBoolPref("devtools.layout.flexbox.opened", true);
+Services.prefs.setBoolPref("devtools.layout.boxmodel.opened", false);
+Services.prefs.setBoolPref("devtools.layout.grid.opened", false);
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("devtools.layout.flexbox.opened");
+  Services.prefs.clearUserPref("devtools.layout.boxmodel.opened");
+  Services.prefs.clearUserPref("devtools.layout.grid.opened");
+});
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -141,16 +141,17 @@ function openLayoutView() {
     }
     mockHighlighter(data.toolbox);
 
     return {
       toolbox: data.toolbox,
       inspector: data.inspector,
       boxmodel: data.inspector.getPanel("boxmodel"),
       gridInspector: data.inspector.layoutview.gridInspector,
+      flexboxInspector: data.inspector.layoutview.flexboxInspector,
       layoutView: data.inspector.layoutview,
       testActor: data.testActor
     };
   });
 }
 
 /**
  * Select the rule view sidebar tab on an already opened inspector panel.
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -102,16 +102,17 @@ devtools.jar:
     skin/dark-theme.css (themes/dark-theme.css)
     skin/light-theme.css (themes/light-theme.css)
     skin/toolbars.css (themes/toolbars.css)
     skin/toolbox.css (themes/toolbox.css)
     skin/tooltips.css (themes/tooltips.css)
     skin/images/accessibility.svg (themes/images/accessibility.svg)
     skin/images/add.svg (themes/images/add.svg)
     skin/images/arrowhead-left.svg (themes/images/arrowhead-left.svg)
+    skin/images/arrowhead-right.svg (themes/images/arrowhead-right.svg)
     skin/images/arrowhead-down.svg (themes/images/arrowhead-down.svg)
     skin/images/arrowhead-up.svg (themes/images/arrowhead-up.svg)
     skin/images/breadcrumbs-divider.svg (themes/images/breadcrumbs-divider.svg)
     skin/images/filters.svg (themes/images/filters.svg)
     skin/images/filter-swatch.svg (themes/images/filter-swatch.svg)
     skin/images/aboutdebugging-collapse-icon.svg (themes/images/aboutdebugging-collapse-icon.svg)
     skin/images/aboutdebugging-connect-icon.svg (themes/images/aboutdebugging-connect-icon.svg)
     skin/images/aboutdebugging-firefox-aurora.svg (themes/images/aboutdebugging-firefox-aurora.svg)
@@ -273,16 +274,17 @@ devtools.jar:
     skin/images/import.svg (themes/images/import.svg)
     skin/images/pane-collapse.svg (themes/images/pane-collapse.svg)
     skin/images/pane-expand.svg (themes/images/pane-expand.svg)
     skin/images/help.svg (themes/images/help.svg)
     skin/images/read-only.svg (themes/images/read-only.svg)
     skin/images/reveal.svg (themes/images/reveal.svg)
     skin/images/select-arrow.svg (themes/images/select-arrow.svg)
     skin/images/settings.svg (themes/images/settings.svg)
+    skin/images/lock.svg (themes/images/lock.svg)
 
     # Debugger
     skin/images/debugger/angular.svg (themes/images/debugger/angular.svg)
     skin/images/debugger/arrow.svg (themes/images/debugger/arrow.svg)
     skin/images/debugger/blackBox.svg (themes/images/debugger/blackBox.svg)
     skin/images/debugger/breakpoint.svg (themes/images/debugger/breakpoint.svg)
     skin/images/debugger/close.svg (themes/images/debugger/close.svg)
     skin/images/debugger/coffeescript.svg (themes/images/debugger/coffeescript.svg)
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -18,17 +18,16 @@ const {
   text,
   wire,
   showFilePicker,
 } = require("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
 const {SplitView} = require("resource://devtools/client/shared/SplitView.jsm");
 const {StyleSheetEditor} = require("resource://devtools/client/styleeditor/StyleSheetEditor.jsm");
 const {PluralForm} = require("devtools/shared/plural-form");
 const {PrefObserver} = require("devtools/client/shared/prefs");
-const csscoverage = require("devtools/shared/fronts/csscoverage");
 const {KeyCodes} = require("devtools/client/shared/keycodes");
 const {OriginalSource} = require("devtools/client/styleeditor/original-source");
 
 loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
 loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
 
 const LOAD_ERROR = "error-load";
 const STYLE_EDITOR_TEMPLATE = "stylesheet";
@@ -631,17 +630,17 @@ StyleEditorUI.prototype = {
             await showEditor.load(inputElement, this._cssProperties);
           }
 
           showEditor.onShow();
 
           this.emit("editor-selected", showEditor);
 
           // Is there any CSS coverage markup to include?
-          const usage = await csscoverage.getUsage(this._target);
+          const usage = this._target.getFront("cssUsage");
           if (usage == null) {
             return;
           }
 
           const sheet = showEditor.styleSheet;
           const {reports} = await usage.createEditorReportForSheet(sheet);
 
           showEditor.removeAllUnusedRegions();
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/arrowhead-right.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path fill="context-fill #0C0C0D" fill-rule="evenodd" clip-rule="evenodd" d="M8.293 4.293a1 1 0 0 1 1.414 0l7 7a1 1 0 0 1 0 1.414l-7 7a1 1 0 0 1-1.414-1.414L14.586 12 8.293 5.707a1 1 0 0 1 0-1.414z"></path>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/lock.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="context-fill">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M9 7a3 3 0 1 1 6 0v3H9V7zm-2 3V7a5 5 0 1 1 10 0v3h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h1z"></path>
+</svg>
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -154,19 +154,196 @@
   padding-top: 0.15em;
   text-align: left;
   text-overflow: ellipsis;
   white-space: nowrap;
   width: 85%;
 }
 
 /**
+ * Flex Item Sizing Outline
+ */
+
+.flex-outline-container {
+  display: flex;
+  justify-content: center;
+}
+
+.flex-outline {
+  display: grid;
+  margin: 2em 0;
+  grid-auto-rows: 35px;
+  flex-basis: 80%;
+  max-width: 450px;
+}
+
+.flex-outline.column {
+  transform: translate(50%, -2em) rotate(.25turn);
+  transform-origin: center left;
+  flex-basis: 150px;
+  margin-bottom: 120px;
+}
+
+.flex-outline-final,
+.flex-outline-basis,
+.flex-outline-delta {
+  grid-row: 1;
+}
+
+.flex-outline-final {
+  border: 1px solid currentColor;
+  position: relative;
+  grid-column: final-start / final-end;
+}
+
+.flex-outline-final.clamped::after {
+  content: "";
+  background-color: var(--theme-body-background);
+  background-image: url(chrome://devtools/skin/images/lock.svg);
+  background-size: 16px;
+  background-repeat: no-repeat;
+  background-position: center 1px;
+  fill: currentColor;
+  -moz-context-properties: fill;
+  width: 20px;
+  height: 20px;
+  position: absolute;
+  right: -10px;
+  top: 6px;
+  border-radius: 50%;
+}
+
+.flex-outline.column .flex-outline-final.clamped::after {
+  transform: rotate(-.25turn);
+}
+
+.flex-outline-basis {
+  border-style: dotted;
+  border-width: 3px;
+  margin: 1px 0;
+  grid-column: basis-start / basis-end;
+}
+
+.flex-outline-basis.zero-basis {
+  border-width: 0 0 0 3px;
+}
+
+.flex-outline-delta {
+  background-repeat: round;
+  fill: currentColor;
+  -moz-context-properties: fill;
+  grid-column: delta-start / delta-end;
+  margin: 4px;
+}
+
+.flex-outline.growing .flex-outline-delta {
+  background-image: url(chrome://devtools/skin/images/arrowhead-right.svg);
+}
+
+.flex-outline.shrinking .flex-outline-delta {
+  background-image: url(chrome://devtools/skin/images/arrowhead-left.svg);
+}
+
+.flex-outline-point {
+  position: relative;
+  -moz-user-select: none;
+}
+
+.flex-outline-point {
+  grid-row: 1;
+}
+
+.flex-outline-point.basis {
+  grid-column-end: basis-end;
+  justify-self: end;
+}
+
+.flex-outline.shrinking .flex-outline-point.basis {
+  grid-column-start: basis-end;
+  justify-self: start;
+}
+
+.flex-outline-point.final {
+  grid-column-start: final-end;
+  left: -1px;
+}
+
+.flex-outline.shrinking .flex-outline-point.final {
+  grid-column-end: final-end;
+  grid-column-start: unset;
+  justify-self: end;
+}
+
+.flex-outline-point.min {
+  grid-column: min;
+  place-self: end;
+}
+
+.flex-outline.shrinking .flex-outline-point.min {
+  place-self: end start;
+}
+
+.flex-outline-point.max {
+  grid-column: max;
+  align-self: end;
+  left: -1px;
+}
+
+.flex-outline-point::before {
+  content: attr(data-label);
+  display: block;
+  position: relative;
+  padding: 0 3px;
+  height: 10px;
+  border-color: currentColor;
+  border-style: solid;
+  border-width: 0;
+}
+
+.flex-outline.column .flex-outline-point::before {
+  padding: 0;
+  writing-mode: sideways-lr;
+}
+
+.flex-outline-point.basis::before,
+.flex-outline-point.final::before {
+  top: -12px;
+}
+
+.flex-outline-point.min::before,
+.flex-outline-point.max::before {
+  bottom: -12px;
+}
+
+.flex-outline.column .flex-outline-point.min::before,
+.flex-outline.column .flex-outline-point.min::before {
+  text-indent: -12px;
+}
+
+.flex-outline-point.final::before,
+.flex-outline.shrinking .flex-outline-point.min::before,
+.flex-outline-point.max::before,
+.flex-outline.shrinking .flex-outline-point.basis::before {
+  border-width: 0 0 0 1px;
+}
+
+.flex-outline-point.basis::before,
+.flex-outline-point.min::before,
+.flex-outline.shrinking .flex-outline-point.final::before {
+  border-width: 0 1px 0 0;
+}
+
+/**
  * Flex Item Sizing Properties
  */
 
+#flex-item-sizing-properties {
+  padding-top: 0;
+}
+
 #flex-item-sizing-properties span {
  font-weight: 600;
 }
 
 /**
  * Flex Container Properties
  */
 
--- a/devtools/shared/fronts/csscoverage.js
+++ b/devtools/shared/fronts/csscoverage.js
@@ -100,26 +100,8 @@ const CSSUsageFront = protocol.FrontClas
    * We count STARTING and STOPPING as 'running'
    */
   isRunning: function() {
     return isRunning;
   }
 });
 
 exports.CSSUsageFront = CSSUsageFront;
-
-const knownFronts = new WeakMap();
-
-/**
- * Create a CSSUsageFront only when needed (returns a promise)
- * For notes on target.attach(), see
- * https://bugzilla.mozilla.org/show_bug.cgi?id=1016330#c7
- */
-exports.getUsage = function(trgt) {
-  return trgt.attach().then(() => {
-    let front = knownFronts.get(trgt.client);
-    if (front == null && trgt.form.cssUsageActor != null) {
-      front = new CSSUsageFront(trgt.client, trgt.form);
-      knownFronts.set(trgt.client, front);
-    }
-    return front;
-  });
-};
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -482,16 +482,17 @@ webgl::LinkedProgramInfo::GetDrawFetchLi
         uint32_t i = 0;
         for (const auto& cur : vao->mAttribs) {
             if (cur.mEnabled && !cur.mBuf) {
                 webgl->ErrorInvalidOperation("Vertex attrib array %u is enabled but"
                                              " has no buffer bound.",
                                              i);
                 return nullptr;
             }
+            i++;
         }
     }
 
     bool hasActiveAttrib = false;
     bool hasActiveDivisor0 = false;
     webgl::CachedDrawFetchLimits fetchLimits = { UINT64_MAX, UINT64_MAX };
 
     for (const auto& progAttrib : this->attribs) {
--- a/js/src/tests/non262/shell.js
+++ b/js/src/tests/non262/shell.js
@@ -106,49 +106,53 @@
         items[0] = items[i];
         items[i] = swap;
         for (let e of Permutations(items.slice(1, items.length)))
           yield [items[0]].concat(e);
       }
     }
   };
 
-  global.assertThrowsValue = function assertThrowsValue(f, val, msg) {
-    var fullmsg;
-    try {
-      f();
-    } catch (exc) {
-      if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
-        return;
-      fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
-    }
-    if (fullmsg === undefined)
-      fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
-    if (msg !== undefined)
-      fullmsg += " - " + msg;
-    throw new Error(fullmsg);
-  };
+  if (typeof global.assertThrowsValue === 'undefined') {
+    global.assertThrowsValue = function assertThrowsValue(f, val, msg) {
+      var fullmsg;
+      try {
+        f();
+      } catch (exc) {
+        if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
+          return;
+        fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
+      }
+      if (fullmsg === undefined)
+        fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
+      if (msg !== undefined)
+        fullmsg += " - " + msg;
+      throw new Error(fullmsg);
+    };
+  }
 
-  global.assertThrowsInstanceOf = function assertThrowsInstanceOf(f, ctor, msg) {
-    var fullmsg;
-    try {
-      f();
-    } catch (exc) {
-      if (exc instanceof ctor)
-        return;
-      fullmsg = `Assertion failed: expected exception ${ctor.name}, got ${exc}`;
-    }
+  if (typeof global.assertThrowsInstanceOf === 'undefined') {
+    global.assertThrowsInstanceOf = function assertThrowsInstanceOf(f, ctor, msg) {
+      var fullmsg;
+      try {
+        f();
+      } catch (exc) {
+        if (exc instanceof ctor)
+          return;
+        fullmsg = `Assertion failed: expected exception ${ctor.name}, got ${exc}`;
+      }
 
-    if (fullmsg === undefined)
-      fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`;
-    if (msg !== undefined)
-      fullmsg += " - " + msg;
+      if (fullmsg === undefined)
+        fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`;
+      if (msg !== undefined)
+        fullmsg += " - " + msg;
 
-    throw new Error(fullmsg);
-  };
+      throw new Error(fullmsg);
+    };
+  }
 
   global.assertDeepEq = (function(){
     var call = Function.prototype.call,
       Array_isArray = Array.isArray,
       Map_ = Map,
       Error_ = Error,
       Symbol_ = Symbol,
       Map_has = call.bind(Map.prototype.has),
--- a/layout/style/test/chrome/test_display_mode.html
+++ b/layout/style/test/chrome/test_display_mode.html
@@ -25,18 +25,16 @@ function waitOneEvent(element, name) {
   });
 }
 
 function promiseNextTick() {
   return new Promise(resolve => setTimeout(resolve, 0));
 }
 
 add_task(async function() {
-  await waitOneEvent(window, "load");
-
   var iframe = document.getElementById("subdoc");
   var subdoc = iframe.contentDocument;
   var style = subdoc.getElementById("style");
   var bodyComputedStyled = subdoc.defaultView.getComputedStyle(subdoc.body);
   var win = Services.wm.getMostRecentWindow("navigator:browser");
 
   function queryApplies(q) {
     style.setAttribute("media", q);
--- a/layout/style/test/chrome/test_display_mode_reflow.html
+++ b/layout/style/test/chrome/test_display_mode_reflow.html
@@ -25,18 +25,16 @@ function waitOneEvent(element, name) {
   });
 }
 
 function promiseNextTick() {
   return new Promise(resolve => setTimeout(resolve, 0));
 }
 
 add_task(async function() {
-  await waitOneEvent(window, "load");
-
   var iframe = document.getElementById("subdoc");
   var subdoc = iframe.contentDocument;
   var style = subdoc.getElementById("style");
   var bodyComputedStyled = subdoc.defaultView.getComputedStyle(subdoc.body);
   var win = Services.wm.getMostRecentWindow("navigator:browser");
 
   var secondDiv = subdoc.getElementById("b");
   var offsetTop = secondDiv.offsetTop;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -497,17 +497,17 @@ pref("media.peerconnection.turn.disable"
 pref("media.getusermedia.aec_enabled", false);
 pref("media.getusermedia.noise_enabled", false);
 #else
 pref("media.getusermedia.aec_enabled", true);
 pref("media.getusermedia.noise_enabled", true);
 #endif
 pref("media.getusermedia.aec_extended_filter", true);
 pref("media.getusermedia.noise", 1);
-pref("media.getusermedia.agc_enabled", false);
+pref("media.getusermedia.agc_enabled", true);
 pref("media.getusermedia.agc", 3); // kAgcAdaptiveDigital
 // capture_delay: Adjustments for OS-specific input delay (lower bound)
 // playout_delay: Adjustments for OS-specific AudioStream+cubeb+output delay (lower bound)
 // full_duplex: enable cubeb full-duplex capture/playback
 pref("media.navigator.audio.full_duplex", true);
 #if defined(XP_MACOSX)
 pref("media.peerconnection.capture_delay", 50);
 #elif defined(XP_WIN)
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -1765,23 +1765,17 @@ nsIOService::SpeculativeConnectInternal(
     // reward is slim with tcp peers closely located to the browser.
     nsresult rv;
     nsCOMPtr<nsIProtocolProxyService> pps =
             do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIPrincipal> loadingPrincipal = aPrincipal;
 
-    NS_ASSERTION(aPrincipal, "We expect passing a principal here.");
-
-    // If the principal is given, we use this principal directly. Otherwise,
-    // we fallback to use the system principal.
-    if (!aPrincipal) {
-        loadingPrincipal = nsContentUtils::GetSystemPrincipal();
-    }
+    MOZ_ASSERT(aPrincipal, "We expect passing a principal here.");
 
     // dummy channel used to create a TCP connection.
     // we perform security checks on the *real* channel, responsible
     // for any network loads. this real channel just checks the TCP
     // pool if there is an available connection created by the
     // channel we create underneath - hence it's safe to use
     // the systemPrincipal as the loadingPrincipal for this channel.
     nsCOMPtr<nsIChannel> channel;
@@ -1809,38 +1803,24 @@ nsIOService::SpeculativeConnectInternal(
         return pps2->AsyncResolve2(channel, 0, callback, nullptr,
                                    getter_AddRefs(cancelable));
     }
     return pps->AsyncResolve(channel, 0, callback, nullptr,
                              getter_AddRefs(cancelable));
 }
 
 NS_IMETHODIMP
-nsIOService::SpeculativeConnect(nsIURI *aURI,
-                                nsIInterfaceRequestor *aCallbacks)
-{
-    return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, false);
-}
-
-NS_IMETHODIMP
 nsIOService::SpeculativeConnect2(nsIURI *aURI,
                                  nsIPrincipal *aPrincipal,
                                  nsIInterfaceRequestor *aCallbacks)
 {
     return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false);
 }
 
 NS_IMETHODIMP
-nsIOService::SpeculativeAnonymousConnect(nsIURI *aURI,
-                                         nsIInterfaceRequestor *aCallbacks)
-{
-    return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, true);
-}
-
-NS_IMETHODIMP
 nsIOService::SpeculativeAnonymousConnect2(nsIURI *aURI,
                                           nsIPrincipal *aPrincipal,
                                           nsIInterfaceRequestor *aCallbacks)
 {
     return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true);
 }
 
 /*static*/ bool
--- a/netwerk/base/nsISpeculativeConnect.idl
+++ b/netwerk/base/nsISpeculativeConnect.idl
@@ -23,26 +23,20 @@ interface nsISpeculativeConnect : nsISup
      *
      * @param aURI the URI of the hinted transaction
      * @param aPrincipal the principal that will be used for opening the
      *        channel of the hinted transaction.
      * @param aCallbacks any security callbacks for use with SSL for interfaces
      *        such as nsIBadCertListener. May be null.
      *
      */
-    void speculativeConnect(in nsIURI aURI,
-                            in nsIInterfaceRequestor aCallbacks);
-
     void speculativeConnect2(in nsIURI aURI,
                              in nsIPrincipal aPrincipal,
                              in nsIInterfaceRequestor aCallbacks);
 
-    void speculativeAnonymousConnect(in nsIURI aURI,
-                                     in nsIInterfaceRequestor aCallbacks);
-
     void speculativeAnonymousConnect2(in nsIURI aURI,
                                       in nsIPrincipal aPrincipal,
                                       in nsIInterfaceRequestor aCallbacks);
 };
 
 /**
  * This is used to override the default values for various values (documented
  * inline) to determine whether or not to actually make a speculative
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -2600,38 +2600,24 @@ nsHttpHandler::SpeculativeConnectInterna
         new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr,
                                  originAttributes, usingSSL);
     ci->SetAnonymous(anonymous);
 
     return SpeculativeConnect(ci, aCallbacks);
 }
 
 NS_IMETHODIMP
-nsHttpHandler::SpeculativeConnect(nsIURI *aURI,
-                                  nsIInterfaceRequestor *aCallbacks)
-{
-    return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, false);
-}
-
-NS_IMETHODIMP
 nsHttpHandler::SpeculativeConnect2(nsIURI *aURI,
                                    nsIPrincipal *aPrincipal,
                                    nsIInterfaceRequestor *aCallbacks)
 {
     return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false);
 }
 
 NS_IMETHODIMP
-nsHttpHandler::SpeculativeAnonymousConnect(nsIURI *aURI,
-                                           nsIInterfaceRequestor *aCallbacks)
-{
-    return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, true);
-}
-
-NS_IMETHODIMP
 nsHttpHandler::SpeculativeAnonymousConnect2(nsIURI *aURI,
                                             nsIPrincipal *aPrincipal,
                                             nsIInterfaceRequestor *aCallbacks)
 {
     return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true);
 }
 
 void
--- a/python/mozbuild/mozbuild/backend/tup.py
+++ b/python/mozbuild/mozbuild/backend/tup.py
@@ -824,16 +824,22 @@ class TupBackend(CommonBackend):
             env['MOZ_AUTOMATION'] = os.environ['MOZ_AUTOMATION']
 
         return env
 
     def _gen_cargo_rules(self, obj,  build_plan, cargo_env, output_group):
         invocations = build_plan['invocations']
         processed = set()
 
+        # Enable link-time optimization for release builds.
+        cargo_library_flags = []
+        if (not obj.config.substs.get('DEVELOPER_OPTIONS') and
+            not obj.config.substs.get('MOZ_DEBUG_RUST')):
+            cargo_library_flags += ['-C', 'lto']
+
         rust_build_home = mozpath.join(self.environment.topobjdir,
                                        'toolkit/library/rust')
 
         def display_name(invocation):
             output_str = ''
             if invocation['outputs']:
                 outputs = [os.path.basename(f) for f in invocation['outputs']]
                 output_str = ' -> [%s]' % self._trim_outputs(outputs)
@@ -868,16 +874,24 @@ class TupBackend(CommonBackend):
             ]
             envvars = invocation.get('env')
             for k, v in itertools.chain(cargo_env.iteritems(),
                                         envvars.iteritems()):
                 command.append("%s=%s" % (k, cargo_quote(v)))
             command.append(invocation['program'])
             command.extend(cargo_quote(a.replace('dep-info,', ''))
                            for a in invocation['args'])
+
+            # This is equivalent to `cargo_rustc_flags` in the make backend,
+            # which are passed to the top-level crate's rustc invocation, in
+            # our case building the static lib.
+            if (invocation['target_kind'][0] == 'staticlib' and
+                obj.basename == shortname):
+                command += cargo_library_flags
+
             outputs = invocation['outputs']
 
             invocation['full-deps'] = set()
 
             if os.path.basename(invocation['program']) == 'build-script-build':
                 out_dir = invocation['env']['OUT_DIR']
                 for output in cargo_extra_outputs.get(shortname, []):
                     outputs.append(os.path.join(out_dir, output))
--- a/taskcluster/docker/pipfile-updates/scripts/update_pipfiles.sh
+++ b/taskcluster/docker/pipfile-updates/scripts/update_pipfiles.sh
@@ -111,16 +111,19 @@ elif [[ "${BRANCH}" == mozilla-* ]]; the
   HGREPO="https://${HGHOST}/releases/${BRANCH}"
 else
   HGREPO="https://${HGHOST}/projects/${BRANCH}"
 fi
 
 clone_repo
 
 ${PIP} install pipenv
+# Bug 1497162
+# Can be removed when https://github.com/pypa/pipenv/issues/2924 is released
+${PIP} install --user pip==18.0
 
 update_pipfile "${PIPFILE_DIRECTORY}"
 echo "INFO: diffing old/new Pipfile.lock into ${DIFF_ARTIFACT}"
 hg -R "${REPODIR}" diff "${BASEDIR}/${BRANCH}/${PIPFILE_DIRECTORY}/Pipfile.lock" | tee "${DIFF_ARTIFACT}"
 
 COMMIT_MESSAGE="No Bug, ${PIPFILE_DIRECTORY} pipfile-update."
 
 if ${HG} -R "${REPODIR}" commit -u "${COMMIT_AUTHOR}" -m "${COMMIT_MESSAGE}"
--- a/testing/mochitest/tests/SimpleTest/AddTask.js
+++ b/testing/mochitest/tests/SimpleTest/AddTask.js
@@ -22,17 +22,30 @@ var add_task = (function () {
         throw new Error("SimpleTest not available.");
       }
       // Don't stop tests until asynchronous tasks are finished.
       SimpleTest.waitForExplicitFinish();
       // Because the client is using add_task for this set of tests,
       // we need to spawn a "master task" that calls each task in succesion.
       // Use setTimeout to ensure the master task runs after the client
       // script finishes.
-      setTimeout(function () {
+      setTimeout(function nextTick() {
+        // If we are in a HTML document, we should wait for the document
+        // to be fully loaded.
+        // These checks ensure that we are in an HTML document without
+        // throwing TypeError; also I am told that readyState in XUL documents
+        // are totally bogus so we don't try to do this there.
+        if (typeof window !== "undefined" &&
+            typeof HTMLDocument !== "undefined" &&
+            window.document instanceof HTMLDocument &&
+            window.document.readyState !== "complete") {
+          setTimeout(nextTick);
+          return;
+        }
+
         (async () => {
           // Allow for a task to be skipped; we need only use the structured logger
           // for this, whilst deactivating log buffering to ensure that messages
           // are always printed to stdout.
           function skipTask(name) {
             let logger = parentRunner && parentRunner.structuredLogger;
             if (!logger) {
               info("AddTask.js | Skipping test " + name);
--- a/testing/tools/minidumpwriter/moz.build
+++ b/testing/tools/minidumpwriter/moz.build
@@ -1,15 +1,15 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-if CONFIG['ENABLE_TESTS'] and CONFIG['CPU_ARCH'] == 'x86_64' and CONFIG['OS_ARCH'] == 'WINNT':
+if CONFIG['ENABLE_TESTS'] and CONFIG['OS_ARCH'] == 'WINNT':
     Program('minidumpwriter')
     OS_LIBS += [
         'dbghelp',
     ]
     SOURCES += [
         'minidumpwriter.cpp',
     ]
     USE_STATIC_LIBS = True
--- a/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_https_upgrade.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_https_upgrade.html
@@ -45,17 +45,17 @@ runInParent(function addLogins() {
     assert.ok(false, "addLogin threw: " + e);
   }
 });
 </script>
 <p id="display"></p>
 
 <!-- we presumably can't hide the content for this test. -->
 <div id="content">
-  <iframe src="https://example.org/tests/toolkit/components/passwordmgr/test/mochitest/form_basic.html"></iframe>
+  <iframe></iframe>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
 let iframeDoc;
 let uname;
 let pword;
@@ -73,16 +73,17 @@ function checkACForm(expectedUsername, e
   let formID = uname.parentNode.id;
   is(uname.value, expectedUsername, "Checking " + formID + " username");
   is(pword.value, expectedPassword, "Checking " + formID + " password");
 }
 
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({"set": [["signon.schemeUpgrades", true]]});
 
+  iframe.src = "https://example.org/tests/toolkit/components/passwordmgr/test/mochitest/form_basic.html";
   await new Promise(resolve => {
     iframe.addEventListener("load", function() {
       resolve();
     }, {once: true});
   });
 
   iframeDoc = iframe.contentDocument;
   uname = iframeDoc.getElementById("form-basic-username");
--- a/toolkit/components/passwordmgr/test/mochitest/test_autofocus_js.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autofocus_js.html
@@ -33,26 +33,27 @@ runInParent(function addLogins() {
   } catch (e) {
     assert.ok(false, "addLogin threw: " + e);
   }
 });
 </script>
 <p id="display"></p>
 
 <div id="content">
-  <iframe src="https://example.org/tests/toolkit/components/passwordmgr/test/mochitest/form_autofocus_js.html"></iframe>
+  <iframe></iframe>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
 let iframeDoc;
 
 add_task(async function setup() {
+  iframe.src = "https://example.org/tests/toolkit/components/passwordmgr/test/mochitest/form_autofocus_js.html";
   await new Promise(resolve => {
     iframe.addEventListener("load", function() {
       resolve();
     }, {once: true});
   });
 
   iframeDoc = iframe.contentDocument;
 
--- a/toolkit/content/tests/widgets/test_videocontrols_error.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_error.html
@@ -18,17 +18,16 @@
 <pre id="test">
 <script clas="testbody" type="application/javascript">
   const video = document.getElementById("video");
   const statusOverlay = getElementWithinVideo(video, "statusOverlay");
   const statusIcon = getElementWithinVideo(video, "statusIcon");
   const statusLabelErrorNoSource = getElementWithinVideo(video, "errorNoSource");
 
   add_task(async function setup() {
-    await new Promise(resolve => window.addEventListener("load", resolve, {once: true}));
     await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
   });
 
   add_task(async function check_normal_status() {
     await new Promise(resolve => {
       video.src = "seek_with_sound.ogg";
       video.addEventListener("loadedmetadata", () => SimpleTest.executeSoon(resolve));
     });
--- a/toolkit/content/tests/widgets/test_videocontrols_video_noaudio.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_video_noaudio.html
@@ -17,17 +17,16 @@
 
 <pre id="test">
 <script clas="testbody" type="application/javascript">
   const video = document.getElementById("video");
   const muteButton = getElementWithinVideo(video, "muteButton");
   const volumeStack = getElementWithinVideo(video, "volumeStack");
 
   add_task(async function setup() {
-    await new Promise(resolve => window.addEventListener("load", resolve));
     await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
     await new Promise(resolve => {
       video.src = "video.ogg";
       video.addEventListener("loadedmetadata", () => SimpleTest.executeSoon(resolve));
     });
   });
 
   add_task(async function mute_button_icon() {
--- a/toolkit/content/tests/widgets/test_videocontrols_vtt.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_vtt.html
@@ -19,17 +19,16 @@
 <script clas="testbody" type="application/javascript">
   SimpleTest.waitForExplicitFinish();
 
   const video = document.getElementById("video");
   const ccBtn = getElementWithinVideo(video, "closedCaptionButton");
   const ttList = getElementWithinVideo(video, "textTrackList");
 
   add_task(async function wait_for_media_ready() {
-    await new Promise(resolve => window.addEventListener("load", resolve, {once: true}));
     await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
     await new Promise(resolve => {
       video.src = "seek_with_sound.ogg";
       video.addEventListener("loadedmetadata", resolve);
     });
   });
 
   add_task(async function check_inital_state() {
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_error.html
+++ b/toolkit/content/tests/widgets/xbl/test_videocontrols_error.html
@@ -17,17 +17,16 @@
 
 <pre id="test">
 <script clas="testbody" type="application/javascript">
   const video = document.getElementById("video");
   const statusOverlay = getAnonElementWithinVideoByAttribute(video, "anonid", "statusOverlay");
   const statusIcon = getAnonElementWithinVideoByAttribute(video, "anonid", "statusIcon");
 
   add_task(async function setup() {
-    await new Promise(resolve => window.addEventListener("load", resolve, {once: true}));
     await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
   });
 
   add_task(async function check_normal_status() {
     await new Promise(resolve => {
       video.src = "seek_with_sound.ogg";
       video.addEventListener("loadedmetadata", () => SimpleTest.executeSoon(resolve));
     });
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_video_noaudio.html
+++ b/toolkit/content/tests/widgets/xbl/test_videocontrols_video_noaudio.html
@@ -17,17 +17,16 @@
 
 <pre id="test">
 <script clas="testbody" type="application/javascript">
   const video = document.getElementById("video");
   const muteButton = getAnonElementWithinVideoByAttribute(video, "anonid", "muteButton");
   const volumeStack = getAnonElementWithinVideoByAttribute(video, "anonid", "volumeStack");
 
   add_task(async function setup() {
-    await new Promise(resolve => window.addEventListener("load", resolve));
     await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
     await new Promise(resolve => {
       video.src = "video.ogg";
       video.addEventListener("loadedmetadata", () => SimpleTest.executeSoon(resolve));
     });
   });
 
   add_task(async function mute_button_icon() {
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_vtt.html
+++ b/toolkit/content/tests/widgets/xbl/test_videocontrols_vtt.html
@@ -19,17 +19,16 @@
 <script clas="testbody" type="application/javascript">
   SimpleTest.waitForExplicitFinish();
 
   const video = document.getElementById("video");
   const ccBtn = getAnonElementWithinVideoByAttribute(video, "anonid", "closedCaptionButton");
   const ttList = getAnonElementWithinVideoByAttribute(video, "anonid", "textTrackList");
 
   add_task(async function wait_for_media_ready() {
-    await new Promise(resolve => window.addEventListener("load", resolve, {once: true}));
     await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
     await new Promise(resolve => {
       video.src = "seek_with_sound.ogg";
       video.addEventListener("loadedmetadata", resolve);
     });
   });
 
   add_task(async function check_inital_state() {
--- a/toolkit/content/widgets/findbar.js
+++ b/toolkit/content/widgets/findbar.js
@@ -43,20 +43,23 @@ class MozFindbar extends XULElement {
         <image anonid="find-status-icon" class="findbar-find-fast find-status-icon" />
         <description anonid="find-status" control="findbar-textbox" class="findbar-find-fast findbar-find-status" />
       </hbox>
       <toolbarbutton anonid="find-closebutton" class="findbar-closebutton close-icon" data-l10n-id="findbar-find-button-close" oncommand="close();" />
     `);
   }
 
   connectedCallback() {
+    // Hide the findbar immediately without animation. This prevents a flicker in the case where
+    // we'll never be shown (i.e. adopting a tab that has a previously-opened-but-now-closed
+    // findbar into a new window).
+    this.setAttribute("noanim", "true");
+    this.hidden = true;
     this.appendChild(document.importNode(this.content, true));
 
-    this.hidden = true;
-
     /**
      * Please keep in sync with toolkit/modules/FindBarChild.jsm
      */
     this.FIND_NORMAL = 0;
 
     this.FIND_TYPEAHEAD = 1;
 
     this.FIND_LINKS = 2;
--- a/toolkit/modules/AppMenuNotifications.jsm
+++ b/toolkit/modules/AppMenuNotifications.jsm
@@ -117,16 +117,19 @@ var AppMenuNotifications = {
       notifications = this._notifications.filter(n => n.id == id);
     } else {
       // If it's not a string, assume RegExp
       notifications = this._notifications.filter(n => id.test(n.id));
     }
 
     notifications.forEach(n => {
       n.dismissed = true;
+      if (n.options.onDismissed) {
+        n.options.onDismissed();
+      }
     });
     this._updateNotifications();
   },
 
   callMainAction(win, notification, fromDoorhanger) {
     let action = notification.mainAction;
     this._callAction(win, notification, action, fromDoorhanger);
   },
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -192,17 +192,17 @@ function makeRegularTest(options, what) 
         props: {state: "STATE_INSTALLING"},
       },
       {
         event: "onInstallEnded",
         props: {state: "STATE_INSTALLED"},
       },
     ];
 
-    let promptPromise = promiseNotification("addon-installed");
+    let promptPromise = acceptAppMenuNotificationWhenShown("addon-installed");
 
     await testInstall(browser, options, steps, what);
 
     await promptPromise;
 
     let version = Services.prefs.getIntPref("webapitest.active_version");
     is(version, 1, "the install really did work");
 
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_theme.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_theme.js
@@ -15,17 +15,17 @@ add_task(async function test_theme_insta
     function observer(subject, topic, data) {
       updates.push(data);
     }
     Services.obs.addObserver(observer, "lightweight-theme-styling-update");
     registerCleanupFunction(() => {
       Services.obs.removeObserver(observer, "lightweight-theme-styling-update");
     });
 
-    let promptPromise = promiseNotification("addon-installed");
+    let promptPromise = acceptAppMenuNotificationWhenShown("addon-installed");
 
     let installPromise = ContentTask.spawn(browser, URL, async (url) => {
       let install = await content.navigator.mozAddonManager.createInstall({url});
       return install.install();
     });
 
     await promptPromise;
     await installPromise;
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -1382,8 +1382,30 @@ function promiseNotification(id = "addon
         PopupNotifications.panel.removeEventListener("popupshown", popupshown);
         PopupNotifications.panel.firstElementChild.button.click();
         resolve();
       }
     }
     PopupNotifications.panel.addEventListener("popupshown", popupshown);
   });
 }
+
+function acceptAppMenuNotificationWhenShown(id) {
+  ChromeUtils.import("resource://gre/modules/AppMenuNotifications.jsm");
+  return new Promise(resolve => {
+    function popupshown() {
+      let notification = AppMenuNotifications.activeNotification;
+      if (!notification) { return; }
+
+      is(notification.id, id, `${id} notification shown`);
+      ok(PanelUI.isNotificationPanelOpen, "notification panel open");
+
+      PanelUI.notificationPanel.removeEventListener("popupshown", popupshown);
+
+      let popupnotificationID = PanelUI._getPopupId(notification);
+      let popupnotification = document.getElementById(popupnotificationID);
+      popupnotification.button.click();
+
+      resolve();
+    }
+    PanelUI.notificationPanel.addEventListener("popupshown", popupshown);
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
@@ -70,16 +70,38 @@ async function waitForProgressNotificati
     ok(notification, `Should have seen the right notification`);
     is(notification.button.hasAttribute("disabled"), wantDisabled,
        "The install button should be disabled?");
   }
 
   return PopupNotifications.panel;
 }
 
+function acceptAppMenuNotificationWhenShown(id) {
+  ChromeUtils.import("resource://gre/modules/AppMenuNotifications.jsm");
+  return new Promise(resolve => {
+    function popupshown() {
+      let notification = AppMenuNotifications.activeNotification;
+      if (!notification) { return; }
+
+      is(notification.id, id, `${id} notification shown`);
+      ok(PanelUI.isNotificationPanelOpen, "notification panel open");
+
+      PanelUI.notificationPanel.removeEventListener("popupshown", popupshown);
+
+      let popupnotificationID = PanelUI._getPopupId(notification);
+      let popupnotification = document.getElementById(popupnotificationID);
+      popupnotification.button.click();
+
+      resolve();
+    }
+    PanelUI.notificationPanel.addEventListener("popupshown", popupshown);
+  });
+}
+
 async function waitForNotification(aId, aExpectedCount = 1) {
   info("Waiting for " + aId + " notification");
 
   let topic = getObserverTopic(aId);
 
   let observerPromise;
   if (aId !== "addon-webext-permissions") {
     observerPromise = new Promise(resolve => {
@@ -118,16 +140,19 @@ async function waitForNotification(aId, 
     let notification = nodes.find(n => n.id == aId + "-notification");
     ok(notification, "Should have seen the " + aId + " notification");
   }
 
   return PopupNotifications.panel;
 }
 
 function waitForNotificationClose() {
+  if (!PopupNotifications.isPanelOpen) {
+    return Promise.resolve();
+  }
   return new Promise(resolve => {
     info("Waiting for notification to close");
     PopupNotifications.panel.addEventListener("popuphidden", function() {
       resolve();
     }, {once: true});
   });
 }
 
@@ -224,19 +249,19 @@ async function test_blockedInstall() {
   EventUtils.synthesizeMouse(notification.button, 20, 10, {});
   // Notification should have changed to progress notification
   ok(PopupNotifications.isPanelOpen, "Notification should still be open");
   notification = panel.childNodes[0];
   is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
 
   let installDialog = await dialogPromise;
 
-  notificationPromise = waitForNotification("addon-installed");
+  notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   installDialog.button.click();
-  panel = await notificationPromise;
+  await notificationPromise;
 
   let installs = await AddonManager.getAllInstalls();
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("amosigned-xpi@tests.mozilla.org");
   addon.uninstall();
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
@@ -258,17 +283,17 @@ async function test_whitelistedInstall()
                                                 + triggers).then(newTab => tab = newTab);
   await progressPromise;
   let installDialog = await dialogPromise;
   await BrowserTestUtils.waitForCondition(() => !!tab, "tab should be present");
 
   is(gBrowser.selectedTab, tab,
      "tab selected in response to the addon-install-confirmation notification");
 
-  let notificationPromise = waitForNotification("addon-installed");
+  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   acceptInstallDialog(installDialog);
   await notificationPromise;
 
   let installs = await AddonManager.getAllInstalls();
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("amosigned-xpi@tests.mozilla.org");
   addon.uninstall();
@@ -354,17 +379,17 @@ async function test_restartless() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "restartless.xpi",
   }));
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
   BrowserTestUtils.loadURI(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let installDialog = await dialogPromise;
 
-  let notificationPromise = waitForNotification("addon-installed");
+  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   acceptInstallDialog(installDialog);
   await notificationPromise;
 
   let installs = await AddonManager.getAllInstalls();
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org");
   addon.uninstall();
@@ -471,17 +496,17 @@ async function test_allUnverified() {
   let message = notification.getAttribute("label");
   is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
 
   let container = document.getElementById("addon-install-confirmation-content");
   is(container.children.length, 1, "Should be one item listed");
   is(container.children[0].firstElementChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
   is(container.children[0].children.length, 1, "Shouldn't have the unverified marker");
 
-  let notificationPromise = waitForNotification("addon-installed");
+  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   acceptInstallDialog(installDialog);
   await notificationPromise;
 
   let addon = await AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org");
   addon.uninstall();
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
   await removeTabAndWaitForNotificationClose();
@@ -582,17 +607,17 @@ async function test_urlBar() {
   await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   gURLBar.value = TESTROOT + "amosigned.xpi";
   gURLBar.focus();
   EventUtils.synthesizeKey("KEY_Enter");
 
   await progressPromise;
   let installDialog = await dialogPromise;
 
-  let notificationPromise = waitForNotification("addon-installed");
+  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   installDialog.button.click();
   await notificationPromise;
 
   let installs = await AddonManager.getAllInstalls();
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("amosigned-xpi@tests.mozilla.org");
   addon.uninstall();
--- a/toolkit/mozapps/extensions/test/xpinstall/head.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/head.js
@@ -108,16 +108,17 @@ var Harness = {
       Services.obs.addObserver(this, "addon-install-failed");
       Services.obs.addObserver(this, "addon-install-complete");
 
       AddonManager.addInstallListener(this);
 
       Services.wm.addListener(this);
 
       window.addEventListener("popupshown", this);
+      PanelUI.notificationPanel.addEventListener("popupshown", this);
 
       var self = this;
       registerCleanupFunction(async function() {
         Services.prefs.clearUserPref(PREF_LOGGING_ENABLED);
         Services.prefs.clearUserPref(PREF_INSTALL_REQUIRESECUREORIGIN);
         Services.obs.removeObserver(self, "addon-install-started");
         Services.obs.removeObserver(self, "addon-install-disabled");
         // Services.obs.removeObserver(self, "addon-install-cancelled");
@@ -126,32 +127,38 @@ var Harness = {
         Services.obs.removeObserver(self, "addon-install-failed");
         Services.obs.removeObserver(self, "addon-install-complete");
 
         AddonManager.removeInstallListener(self);
 
         Services.wm.removeListener(self);
 
         window.removeEventListener("popupshown", self);
+        PanelUI.notificationPanel.removeEventListener("popupshown", self);
 
         let aInstalls = await AddonManager.getAllInstalls();
         is(aInstalls.length, 0, "Should be no active installs at the end of the test");
         aInstalls.forEach(function(aInstall) {
           info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state);
           aInstall.cancel();
         });
       });
     }
 
     this.installCount = 0;
     this.pendingCount = 0;
     this.runningInstalls = [];
   },
 
   finish() {
+    // Some tests using this harness somehow finish leaving
+    // the addon-installed panel open.  hiding here addresses
+    // that which fixes the rest of the tests.  Since no test
+    // here cares about this panel, we just need it to close.
+    PanelUI.notificationPanel.hidePopup();
     finish();
   },
 
   endTest() {
     let callback = this.installsCompletedCallback;
     let count = this.installCount;
 
     is(this.runningInstalls.length, 0, "Should be no running installs left");
@@ -231,21 +238,23 @@ var Harness = {
       panel.secondaryButton.click();
     } else {
       panel.button.click();
     }
   },
 
   handleEvent(event) {
     if (event.type === "popupshown") {
-      if (event.target.firstElementChild) {
+      if (event.target == PanelUI.notificationPanel) {
+        PanelUI.notificationPanel.hidePopup();
+      } else if (event.target.firstElementChild) {
         let popupId = event.target.getAttribute("popupid");
         if (popupId === "addon-webext-permissions") {
           this.popupReady(event.target.firstElementChild);
-        } else if (popupId === "addon-installed" || popupId === "addon-install-failed") {
+        } else if (popupId === "addon-install-failed") {
           event.target.firstElementChild.button.click();
         }
       }
     }
   },
 
   // Install blocked handling