Merge mozilla-central to inbound. a=merge CLOSED TREE
authorNoemi Erli <nerli@mozilla.com>
Thu, 04 Oct 2018 07:11:04 +0300
changeset 495329 2cf14f576b8aaae2586a42a0048740dd542e81f3
parent 495328 4c0026439db7b741f41200c61cd18358094cbeb0 (current diff)
parent 495241 8b1f1ebed0f0d6c8abc7e201d70d999f92f2817e (diff)
child 495330 649bc3951808d3b6503c44a91475d412c71d1516
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
toolkit/content/xul.css
toolkit/themes/shared/datetimepopup.css
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -109,49 +109,49 @@ xmlns="http://www.w3.org/1999/xhtml"
 #include browser-sets.inc
 
   <popupset id="mainPopupSet">
     <menupopup id="tabContextMenu"
                onpopupshowing="if (event.target == this) TabContextMenu.updateContextMenu(this);"
                onpopuphidden="if (event.target == this) TabContextMenu.contextTab = null;">
       <menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
                 oncommand="gBrowser.reloadTab(TabContextMenu.contextTab);"/>
-      <menuitem id="context_reloadSelectedTabs" label="&reloadSelectedTabs.label;" hidden="true"
-                accesskey="&reloadSelectedTabs.accesskey;"
+      <menuitem id="context_reloadSelectedTabs" label="&reloadTabs.label;" hidden="true"
+                accesskey="&reloadTabs.accesskey;"
                 oncommand="gBrowser.reloadMultiSelectedTabs();"/>
       <menuitem id="context_toggleMuteTab" oncommand="TabContextMenu.contextTab.toggleMuteAudio();"/>
       <menuitem id="context_toggleMuteSelectedTabs" hidden="true"
                 oncommand="gBrowser.toggleMuteAudioOnMultiSelectedTabs(TabContextMenu.contextTab);"/>
-      <menuseparator/>
-      <menuitem id="context_selectAllTabs" label="&selectAllTabs.label;" accesskey="&selectAllTabs.accesskey;"
-                oncommand="gBrowser.selectAllTabs();"/>
-      <menuitem id="context_bookmarkSelectedTabs"
-                hidden="true"
-                label="&bookmarkSelectedTabs.label;"
-                accesskey="&bookmarkSelectedTabs.accesskey;"
-                oncommand="PlacesCommandHook.bookmarkPages(PlacesCommandHook.uniqueSelectedPages);"/>
-      <menuitem id="context_bookmarkTab"
-                label="&bookmarkTab.label;"
-                accesskey="&bookmarkTab.accesskey;"
-                oncommand="PlacesCommandHook.bookmarkPages(PlacesCommandHook.getUniquePages([TabContextMenu.contextTab]));"/>
       <menuitem id="context_pinTab" label="&pinTab.label;"
                 accesskey="&pinTab.accesskey;"
                 oncommand="gBrowser.pinTab(TabContextMenu.contextTab);"/>
       <menuitem id="context_unpinTab" label="&unpinTab.label;" hidden="true"
                 accesskey="&unpinTab.accesskey;"
                 oncommand="gBrowser.unpinTab(TabContextMenu.contextTab);"/>
       <menuitem id="context_pinSelectedTabs" label="&pinSelectedTabs.label;" hidden="true"
                 accesskey="&pinSelectedTabs.accesskey;"
                 oncommand="gBrowser.pinMultiSelectedTabs();"/>
       <menuitem id="context_unpinSelectedTabs" label="&unpinSelectedTabs.label;" hidden="true"
                 accesskey="&unpinSelectedTabs.accesskey;"
                 oncommand="gBrowser.unpinMultiSelectedTabs();"/>
       <menuitem id="context_duplicateTab" label="&duplicateTab.label;"
                 accesskey="&duplicateTab.accesskey;"
                 oncommand="duplicateTabIn(TabContextMenu.contextTab, 'tab');"/>
+      <menuseparator/>
+      <menuitem id="context_selectAllTabs" label="&selectAllTabs.label;" accesskey="&selectAllTabs.accesskey;"
+                oncommand="gBrowser.selectAllTabs();"/>
+      <menuitem id="context_bookmarkSelectedTabs"
+                hidden="true"
+                label="&bookmarkSelectedTabs.label;"
+                accesskey="&bookmarkSelectedTabs.accesskey;"
+                oncommand="PlacesCommandHook.bookmarkPages(PlacesCommandHook.uniqueSelectedPages);"/>
+      <menuitem id="context_bookmarkTab"
+                label="&bookmarkTab.label;"
+                accesskey="&bookmarkTab.accesskey;"
+                oncommand="PlacesCommandHook.bookmarkPages(PlacesCommandHook.getUniquePages([TabContextMenu.contextTab]));"/>
       <menu id="context_reopenInContainer"
             label="&reopenInContainer.label;"
             accesskey="&reopenInContainer.accesskey;"
             hidden="true">
         <menupopup oncommand="TabContextMenu.reopenInContainer(event);"
                    onpopupshowing="TabContextMenu.createReopenInContainerMenu(event);"/>
       </menu>
       <menu id="context_moveTabOptions"
@@ -171,17 +171,16 @@ xmlns="http://www.w3.org/1999/xhtml"
                     tbattr="tabbrowser-multiple"
                     oncommand="gBrowser.moveTabsToEnd(TabContextMenu.contextTab);"/>
           <menuitem id="context_openTabInWindow" label="&moveToNewWindow.label;"
                     accesskey="&moveToNewWindow.accesskey;"
                     tbattr="tabbrowser-multiple"
                     oncommand="gBrowser.replaceTabsWithWindow(TabContextMenu.contextTab);"/>
         </menupopup>
       </menu>
-      <menuseparator id="context_sendTabToDevice_separator" class="sync-ui-item"/>
       <menu id="context_sendTabToDevice"
             class="sync-ui-item">
         <menupopup id="context_sendTabToDevicePopupMenu"
                    onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab);"/>
       </menu>
       <menuseparator/>
       <menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
                 tbattr="tabbrowser-multiple-visible"
@@ -198,18 +197,18 @@ xmlns="http://www.w3.org/1999/xhtml"
         </menupopup>
       </menu>
       <menuitem id="context_undoCloseTab"
                 label="&undoCloseTab.label;"
                 accesskey="&undoCloseTab.accesskey;"
                 observes="History:UndoCloseTab"/>
       <menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
                 oncommand="gBrowser.removeTab(TabContextMenu.contextTab, { animate: true });"/>
-      <menuitem id="context_closeSelectedTabs" label="&closeSelectedTabs.label;"
-                hidden="true" accesskey="&closeSelectedTabs.accesskey;"
+      <menuitem id="context_closeSelectedTabs" label="&closeTabs.label;"
+                hidden="true" accesskey="&closeTabs.accesskey;"
                 oncommand="gBrowser.removeMultiSelectedTabs();"/>
     </menupopup>
 
     <!-- bug 415444/582485: event.stopPropagation is here for the cloned version
          of this menupopup -->
     <menupopup id="backForwardMenu"
                onpopupshowing="return FillHistoryMenu(event.target);"
                oncommand="gotoHistoryIndex(event); event.stopPropagation();"
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -4016,18 +4016,23 @@ window._gBrowser = {
 
   pinMultiSelectedTabs() {
     for (let tab of this.selectedTabs) {
         this.pinTab(tab);
     }
   },
 
   unpinMultiSelectedTabs() {
-    for (let tab of this.selectedTabs) {
-        this.unpinTab(tab);
+    // The selectedTabs getter returns the tabs
+    // in visual order. We need to unpin in reverse
+    // order to maintain visual order.
+    let selectedTabs = this.selectedTabs;
+    for (let i = selectedTabs.length - 1; i >= 0; i--) {
+      let tab = selectedTabs[i];
+      this.unpinTab(tab);
     }
   },
 
   activateBrowserForPrintPreview(aBrowser) {
     this._printPreviewBrowsers.add(aBrowser);
     if (this._switcher) {
       this._switcher.activateBrowserForPrintPreview(aBrowser);
     }
@@ -4149,17 +4154,17 @@ window._gBrowser = {
     };
 
     let label;
     const selectedTabs = this.selectedTabs;
     const contextTabInSelection = selectedTabs.includes(tab);
     const affectedTabsLength = contextTabInSelection ? selectedTabs.length : 1;
     if (tab.mOverCloseButton) {
       label = tab.selected ?
-        stringWithShortcut("tabs.closeSelectedTabs.tooltip", "key_close", affectedTabsLength) :
+        stringWithShortcut("tabs.closeTabs.tooltip", "key_close", affectedTabsLength) :
         PluralForm.get(affectedTabsLength, gTabBrowserBundle.GetStringFromName("tabs.closeTabs.tooltip"))
                   .replace("#1", affectedTabsLength);
     } else if (tab._overPlayingIcon) {
       let stringID;
       if (tab.selected) {
         stringID = tab.linkedBrowser.audioMuted ?
           "tabs.unmuteAudio2.tooltip" :
           "tabs.muteAudio2.tooltip";
@@ -5349,18 +5354,18 @@ var TabContextMenu = {
       gBrowser.visibleTabs.filter(t => !t.multiselected && !t.pinned).length :
       gBrowser.visibleTabs.filter(t => t != this.contextTab && !t.pinned).length;
     document.getElementById("context_closeOtherTabs").disabled = unpinnedTabsToClose < 1;
 
     // Only one of close_tab/close_selected_tabs should be visible
     document.getElementById("context_closeTab").hidden = multiselectionContext;
     document.getElementById("context_closeSelectedTabs").hidden = !multiselectionContext;
 
-    // Hide "Close Tab Options" if all tabs are selected
-    document.getElementById("context_closeTabOptions").hidden = gBrowser.allTabsSelected();
+    // Disable "Close Tab Options" if all tabs are selected
+    document.getElementById("context_closeTabOptions").disabled = gBrowser.allTabsSelected();
 
     // Hide "Bookmark Tab" for multiselection.
     // Update its state if visible.
     let bookmarkTab = document.getElementById("context_bookmarkTab");
     bookmarkTab.hidden = multiselectionContext;
 
     // Show "Bookmark Selected Tabs" in a multiselect context and hide it otherwise.
     let bookmarkMultiSelectedTabs = document.getElementById("context_bookmarkSelectedTabs");
--- a/browser/base/content/test/sync/browser_contextmenu_sendtab.js
+++ b/browser/base/content/test/sync/browser_contextmenu_sendtab.js
@@ -100,13 +100,12 @@ add_task(async function test_tab_context
 
 add_task(async function test_tab_contextmenu_fxa_disabled() {
   const getter = sinon.stub(gSync, "SYNC_ENABLED").get(() => false);
   // Simulate onSyncDisabled() being called on window open.
   gSync.onSyncDisabled();
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, true, "Send tab to device is hidden");
-  is(document.getElementById("context_sendTabToDevice_separator").hidden, true, "Separator is also hidden");
 
   getter.restore();
   [...document.querySelectorAll(".sync-ui-item")].forEach(e => e.hidden = false);
 });
--- a/browser/base/content/test/tabs/browser_multiselect_tabs_pin_unpin.js
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_pin_unpin.js
@@ -2,28 +2,27 @@ const PREF_MULTISELECT_TABS = "browser.t
 
 add_task(async function setPref() {
     await SpecialPowers.pushPrefEnv({
         set: [[PREF_MULTISELECT_TABS, true]],
     });
 });
 
 add_task(async function test() {
-    let tab1 = await addTab();
+    let tab1 = gBrowser.selectedTab;
     let tab2 = await addTab();
     let tab3 = await addTab();
 
     let menuItemPinTab = document.getElementById("context_pinTab");
     let menuItemUnpinTab = document.getElementById("context_unpinTab");
     let menuItemPinSelectedTabs = document.getElementById("context_pinSelectedTabs");
     let menuItemUnpinSelectedTabs = document.getElementById("context_unpinSelectedTabs");
 
     is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
 
-    await BrowserTestUtils.switchTab(gBrowser, tab1);
     await triggerClickOn(tab2, { ctrlKey: true });
 
     ok(tab1.multiselected, "Tab1 is multiselected");
     ok(tab2.multiselected, "Tab2 is multiselected");
     ok(!tab3.multiselected, "Tab3 is not multiselected");
 
     // Check the context menu with a non-multiselected tab
     updateTabContextMenu(tab3);
@@ -45,16 +44,19 @@ add_task(async function test() {
     let tab2Pinned = BrowserTestUtils.waitForEvent(tab2, "TabPinned");
     menuItemPinSelectedTabs.click();
     await tab1Pinned;
     await tab2Pinned;
 
     ok(tab1.pinned, "Tab1 is pinned");
     ok(tab2.pinned, "Tab2 is pinned");
     ok(!tab3.pinned, "Tab3 is unpinned");
+    is(tab1._tPos, 0, "Tab1 should still be first after pinning");
+    is(tab2._tPos, 1, "Tab2 should still be second after pinning");
+    is(tab3._tPos, 2, "Tab3 should still be third after pinning");
 
     // Check the context menu with a multiselected and pinned tab
     updateTabContextMenu(tab2);
     ok(tab2.pinned, "Tab2 is pinned");
     is(menuItemPinTab.hidden, true, "Pin Tab is hidden");
     is(menuItemUnpinTab.hidden, true, "Unpin Tab is hidden");
     is(menuItemPinSelectedTabs.hidden, true, "Pin Selected Tabs is hidden");
     is(menuItemUnpinSelectedTabs.hidden, false, "Unpin Selected Tabs is visible");
@@ -63,13 +65,15 @@ add_task(async function test() {
     let tab2Unpinned = BrowserTestUtils.waitForEvent(tab2, "TabUnpinned");
     menuItemUnpinSelectedTabs.click();
     await tab1Unpinned;
     await tab2Unpinned;
 
     ok(!tab1.pinned, "Tab1 is unpinned");
     ok(!tab2.pinned, "Tab2 is unpinned");
     ok(!tab3.pinned, "Tab3 is unpinned");
+    is(tab1._tPos, 0, "Tab1 should still be first after unpinning");
+    is(tab2._tPos, 1, "Tab2 should still be second after unpinning");
+    is(tab3._tPos, 2, "Tab3 should still be third after unpinning");
 
-    BrowserTestUtils.removeTab(tab1);
     BrowserTestUtils.removeTab(tab2);
     BrowserTestUtils.removeTab(tab3);
 });
--- a/browser/base/content/webext-panels.js
+++ b/browser/base/content/webext-panels.js
@@ -14,67 +14,71 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   promiseEvent,
 } = ExtensionUtils;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
-function getBrowser(sidebar) {
+function getBrowser(panel) {
   let browser = document.getElementById("webext-panels-browser");
   if (browser) {
     return Promise.resolve(browser);
   }
 
-  let stack = document.createXULElement("stack");
-  stack.setAttribute("flex", "1");
+  let stack = document.getElementById("webext-panels-stack");
+  if (!stack) {
+    stack = document.createXULElement("stack");
+    stack.setAttribute("flex", "1");
+    stack.setAttribute("id", "webext-panels-stack");
+    document.documentElement.appendChild(stack);
+  }
 
   browser = document.createXULElement("browser");
   browser.setAttribute("id", "webext-panels-browser");
   browser.setAttribute("type", "content");
   browser.setAttribute("flex", "1");
   browser.setAttribute("disableglobalhistory", "true");
-  browser.setAttribute("webextension-view-type", "sidebar");
+  browser.setAttribute("webextension-view-type", panel.viewType);
   browser.setAttribute("context", "contentAreaContextMenu");
   browser.setAttribute("tooltip", "aHTMLTooltip");
   browser.setAttribute("autocompletepopup", "PopupAutoComplete");
   browser.setAttribute("selectmenulist", "ContentSelectDropdown");
 
   // Ensure that the browser is going to run in the same process of the other
   // extension pages from the same addon.
-  browser.sameProcessAsFrameLoader = sidebar.extension.groupFrameLoader;
+  browser.sameProcessAsFrameLoader = panel.extension.groupFrameLoader;
 
   let readyPromise;
-  if (sidebar.extension.remote) {
+  if (panel.extension.remote) {
     browser.setAttribute("remote", "true");
     browser.setAttribute("remoteType",
-                         E10SUtils.getRemoteTypeForURI(sidebar.uri, true,
+                         E10SUtils.getRemoteTypeForURI(panel.uri, true,
                                                        E10SUtils.EXTENSION_REMOTE_TYPE));
     readyPromise = promiseEvent(browser, "XULFrameLoaderCreated");
 
     window.messageManager.addMessageListener("contextmenu", openContextMenu);
     window.addEventListener("unload", () => {
       window.messageManager.removeMessageListener("contextmenu", openContextMenu);
     }, {once: true});
   } else {
     readyPromise = Promise.resolve();
   }
 
   stack.appendChild(browser);
-  document.documentElement.appendChild(stack);
 
   return readyPromise.then(() => {
     browser.messageManager.loadFrameScript("chrome://browser/content/content.js", false, true);
-    ExtensionParent.apiManager.emit("extension-browser-inserted", browser);
+    ExtensionParent.apiManager.emit("extension-browser-inserted", browser, panel.browserInsertedData);
 
     browser.messageManager.loadFrameScript(
       "chrome://extensions/content/ext-browser-content.js", false, true);
 
-    let options = sidebar.browserStyle !== false ? {stylesheets: ExtensionParent.extensionStylesheets} : {};
+    let options = panel.browserStyle !== false ? {stylesheets: ExtensionParent.extensionStylesheets} : {};
     browser.messageManager.sendAsyncMessage("Extension:InitBrowser", options);
     return browser;
   });
 }
 
 // Stub tabbrowser implementation for use by the tab-modal alert code.
 var gBrowser = {
   get selectedBrowser() {
@@ -110,19 +114,22 @@ function loadPanel(extensionId, extensio
     if (browserEl.currentURI.spec === extensionUrl) {
       return;
     }
     // Forces runtime disconnect.  Remove the stack (parent).
     browserEl.parentNode.remove();
   }
 
   let policy = WebExtensionPolicy.getByID(extensionId);
+
   let sidebar = {
     uri: extensionUrl,
     extension: policy.extension,
     browserStyle,
+    viewType: "sidebar",
   };
+
   getBrowser(sidebar).then(browser => {
     let uri = Services.io.newURI(policy.getURL());
     let triggeringPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
     browser.loadURI(extensionUrl, {triggeringPrincipal});
   });
 }
--- a/browser/components/extensions/child/ext-devtools-panels.js
+++ b/browser/components/extensions/child/ext-devtools-panels.js
@@ -1,12 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
+
 ChromeUtils.defineModuleGetter(this, "ExtensionChildDevToolsUtils",
                                "resource://gre/modules/ExtensionChildDevToolsUtils.jsm");
 
 var {
   promiseDocumentLoaded,
 } = ExtensionUtils;
 
 /**
@@ -189,16 +193,33 @@ class ChildDevToolsInspectorSidebar exte
 
   onParentSidebarHidden() {
     this.emit("hidden");
   }
 
   api() {
     const {context, id} = this;
 
+    let extensionURL = new URL("/", context.uri.spec);
+
+    // This is currently needed by sidebar.setPage because API objects are not automatically wrapped
+    // by the API Schema validations and so the ExtensionURL type used in the JSON schema
+    // doesn't have any effect on the parameter received by the setPage API method.
+    function resolveExtensionURL(url) {
+      let sidebarPageURL = new URL(url, context.uri.spec);
+
+      if (extensionURL.protocol !== sidebarPageURL.protocol ||
+          extensionURL.host !== sidebarPageURL.host) {
+        throw new context.cloneScope.Error(
+          `Invalid sidebar URL: ${sidebarPageURL.href} is not a valid extension URL`);
+      }
+
+      return sidebarPageURL.href;
+    }
+
     return {
       onShown: new EventManager({
         context,
         name: "devtoolsInspectorSidebar.onShown",
         register: fire => {
           const listener = (eventName, panelContentWindow) => {
             fire.asyncWithoutClone(panelContentWindow);
           };
@@ -218,16 +239,25 @@ class ChildDevToolsInspectorSidebar exte
           };
           this.on("hidden", listener);
           return () => {
             this.off("hidden", listener);
           };
         },
       }).api(),
 
+      setPage(extensionPageURL) {
+        let resolvedSidebarURL = resolveExtensionURL(extensionPageURL);
+
+        return context.childManager.callParentAsyncFunction(
+          "devtools.panels.elements.Sidebar.setPage",
+          [id, resolvedSidebarURL]
+        );
+      },
+
       setObject(jsonObject, rootTitle) {
         return context.cloneScope.Promise.resolve().then(() => {
           return context.childManager.callParentAsyncFunction(
             "devtools.panels.elements.Sidebar.setObject",
             [id, jsonObject, rootTitle]
           );
         });
       },
--- a/browser/components/extensions/parent/ext-devtools-panels.js
+++ b/browser/components/extensions/parent/ext-devtools-panels.js
@@ -1,90 +1,146 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm");
 
-ChromeUtils.defineModuleGetter(this, "E10SUtils",
-                               "resource://gre/modules/E10SUtils.jsm");
-
 var {
   IconDetails,
   watchExtensionProxyContextLoad,
 } = ExtensionParent;
 
 var {
-  promiseEvent,
+  promiseDocumentLoaded,
 } = ExtensionUtils;
 
+const WEBEXT_PANELS_URL = "chrome://browser/content/webext-panels.xul";
+
+class BaseDevToolsPanel {
+  constructor(context, panelOptions) {
+    const toolbox = context.devToolsToolbox;
+    if (!toolbox) {
+      // This should never happen when this constructor is called with a valid
+      // devtools extension context.
+      throw Error("Missing mandatory toolbox");
+    }
+
+    this.context = context;
+    this.extension = context.extension;
+    this.toolbox = toolbox;
+    this.viewType = "devtools_panel";
+    this.panelOptions = panelOptions;
+    this.id = panelOptions.id;
+
+    this.unwatchExtensionProxyContextLoad = null;
+
+    // References to the panel browser XUL element and the toolbox window global which
+    // contains the devtools panel UI.
+    this.browser = null;
+    this.browserContainerWindow = null;
+  }
+
+  async createBrowserElement(window) {
+    const {toolbox} = this;
+    const {extension} = this.context;
+    const {url} = this.panelOptions || {url: "about:blank"};
+
+    this.browser = await window.getBrowser({
+      extension,
+      extensionUrl: url,
+      browserStyle: false,
+      viewType: "devtools_panel",
+      browserInsertedData: {
+        devtoolsToolboxInfo: {
+          toolboxPanelId: this.id,
+          inspectedWindowTabId: getTargetTabIdForToolbox(toolbox),
+        },
+      },
+    });
+
+    let hasTopLevelContext = false;
+
+    // Listening to new proxy contexts.
+    this.unwatchExtensionProxyContextLoad = watchExtensionProxyContextLoad(this, context => {
+      // Keep track of the toolbox and target associated to the context, which is
+      // needed by the API methods implementation.
+      context.devToolsToolbox = toolbox;
+
+      if (!hasTopLevelContext) {
+        hasTopLevelContext = true;
+
+        // Resolve the promise when the root devtools_panel context has been created.
+        if (this._resolveTopLevelContext) {
+          this._resolveTopLevelContext(context);
+        }
+      }
+    });
+
+    this.browser.loadURI(url, {triggeringPrincipal: this.context.principal});
+  }
+
+  destroyBrowserElement() {
+    const {browser, unwatchExtensionProxyContextLoad} = this;
+    if (unwatchExtensionProxyContextLoad) {
+      this.unwatchExtensionProxyContextLoad = null;
+      unwatchExtensionProxyContextLoad();
+    }
+
+    if (browser) {
+      browser.remove();
+      this.browser = null;
+    }
+  }
+}
+
 /**
  * Represents an addon devtools panel in the main process.
  *
  * @param {ExtensionChildProxyContext} context
  *        A devtools extension proxy context running in a main process.
  * @param {object} options
  * @param {string} options.id
  *        The id of the addon devtools panel.
  * @param {string} options.icon
  *        The icon of the addon devtools panel.
  * @param {string} options.title
  *        The title of the addon devtools panel.
  * @param {string} options.url
  *        The url of the addon devtools panel, relative to the extension base URL.
  */
-class ParentDevToolsPanel {
+class ParentDevToolsPanel extends BaseDevToolsPanel {
   constructor(context, panelOptions) {
-    const toolbox = context.devToolsToolbox;
-    if (!toolbox) {
-      // This should never happen when this constructor is called with a valid
-      // devtools extension context.
-      throw Error("Missing mandatory toolbox");
-    }
-
-    this.extension = context.extension;
-    this.viewType = "devtools_panel";
+    super(context, panelOptions);
 
     this.visible = false;
-    this.toolbox = toolbox;
-
-    this.context = context;
-
-    this.panelOptions = panelOptions;
+    this.destroyed = false;
 
     this.context.callOnClose(this);
 
-    this.id = this.panelOptions.id;
-
     this.onToolboxPanelSelect = this.onToolboxPanelSelect.bind(this);
     this.onToolboxHostWillChange = this.onToolboxHostWillChange.bind(this);
     this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
 
-    this.unwatchExtensionProxyContextLoad = null;
     this.waitTopLevelContext = new Promise(resolve => {
       this._resolveTopLevelContext = resolve;
     });
 
-    // References to the panel browser XUL element and the toolbox window global which
-    // contains the devtools panel UI.
-    this.browser = null;
-    this.browserContainerWindow = null;
-
     this.panelAdded = false;
     this.addPanel();
   }
 
   addPanel() {
     const {icon, title} = this.panelOptions;
     const extensionName = this.context.extension.name;
 
     this.toolbox.addAdditionalTool({
       id: this.id,
       extensionId: this.context.extension.id,
-      url: "chrome://browser/content/webext-panels.xul",
+      url: WEBEXT_PANELS_URL,
       icon: icon,
       label: title,
       tooltip: `DevTools Panel added by "${extensionName}" add-on.`,
       isTargetSupported: target => target.isLocalTab,
       build: (window, toolbox) => {
         if (toolbox !== this.toolbox) {
           throw new Error("Unexpected toolbox received on addAdditionalTool build property");
         }
@@ -188,111 +244,30 @@ class ParentDevToolsPanel {
 
     if (!toolbox) {
       throw new Error("Unable to destroy a closed devtools panel");
     }
 
     // Explicitly remove the panel if it is registered and the toolbox is not
     // closing itself.
     if (this.panelAdded && toolbox.isToolRegistered(this.id)) {
+      this.destroyBrowserElement();
       toolbox.removeAdditionalTool(this.id);
     }
 
     this.waitTopLevelContext = null;
     this._resolveTopLevelContext = null;
     this.context = null;
     this.toolbox = null;
     this.browser = null;
     this.browserContainerWindow = null;
   }
 
-  createBrowserElement(window) {
-    const {toolbox} = this;
-    const {extension} = this.context;
-    const {url} = this.panelOptions;
-    const {document} = window;
-
-    // TODO Bug 1442601: Refactor ext-devtools-panels.js to reuse the helpers
-    // functions defined in webext-panels.xul (e.g. create the browser element
-    // using an helper function defined in webext-panels.js and shared with the
-    // extension sidebar pages).
-    let stack = document.getElementById("webext-panels-stack");
-    if (!stack) {
-      stack = document.createXULElement("stack");
-      stack.setAttribute("flex", "1");
-      stack.setAttribute("id", "webext-panels-stack");
-      document.documentElement.appendChild(stack);
-    }
-
-    const browser = document.createXULElement("browser");
-    browser.setAttribute("id", "webext-panels-browser");
-    browser.setAttribute("type", "content");
-    browser.setAttribute("disableglobalhistory", "true");
-    browser.setAttribute("flex", "1");
-    browser.setAttribute("class", "webextension-devtoolsPanel-browser");
-    browser.setAttribute("webextension-view-type", "devtools_panel");
-    // TODO Bug 1442604: Add additional tests for the select and autocompletion
-    // popups used in an extension devtools panels (in oop and non-oop mode).
-    browser.setAttribute("selectmenulist", "ContentSelectDropdown");
-    browser.setAttribute("autocompletepopup", "PopupAutoComplete");
-
-    // Ensure that the devtools panel browser is going to run in the same
-    // process of the other extension pages from the same addon.
-    browser.sameProcessAsFrameLoader = extension.groupFrameLoader;
-
-    this.browser = browser;
-
-    let awaitFrameLoader = Promise.resolve();
-    if (extension.remote) {
-      browser.setAttribute("remote", "true");
-      browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
-      awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated");
-    }
-
-    let hasTopLevelContext = false;
-
-    // Listening to new proxy contexts.
-    this.unwatchExtensionProxyContextLoad = watchExtensionProxyContextLoad(this, context => {
-      // Keep track of the toolbox and target associated to the context, which is
-      // needed by the API methods implementation.
-      context.devToolsToolbox = toolbox;
-
-      if (!hasTopLevelContext) {
-        hasTopLevelContext = true;
-
-        // Resolve the promise when the root devtools_panel context has been created.
-        awaitFrameLoader.then(() => this._resolveTopLevelContext(context));
-      }
-    });
-
-    stack.appendChild(browser);
-
-    extensions.emit("extension-browser-inserted", browser, {
-      devtoolsToolboxInfo: {
-        toolboxPanelId: this.id,
-        inspectedWindowTabId: getTargetTabIdForToolbox(toolbox),
-      },
-    });
-
-    browser.loadURI(url, {
-      triggeringPrincipal: extension.principal,
-    });
-  }
-
   destroyBrowserElement() {
-    const {browser, unwatchExtensionProxyContextLoad} = this;
-    if (unwatchExtensionProxyContextLoad) {
-      this.unwatchExtensionProxyContextLoad = null;
-      unwatchExtensionProxyContextLoad();
-    }
-
-    if (browser) {
-      browser.remove();
-      this.browser = null;
-    }
+    super.destroyBrowserElement();
 
     // If the panel has been removed or disabled (e.g. from the toolbox preferences
     // or during the toolbox switching between docked and undocked),
     // we need to re-initialize the waitTopLevelContext Promise.
     this.waitTopLevelContext = new Promise(resolve => {
       this._resolveTopLevelContext = resolve;
     });
   }
@@ -355,104 +330,166 @@ class DevToolsSelectionObserver extends 
  * @param {ExtensionChildProxyContext} context
  *        A devtools extension proxy context running in a main process.
  * @param {object} options
  * @param {string} options.id
  *        The id of the addon devtools sidebar.
  * @param {string} options.title
  *        The title of the addon devtools sidebar.
  */
-class ParentDevToolsInspectorSidebar {
-  constructor(context, sidebarOptions) {
-    const toolbox = context.devToolsToolbox;
-    if (!toolbox) {
-      // This should never happen when this constructor is called with a valid
-      // devtools extension context.
-      throw Error("Missing mandatory toolbox");
-    }
+class ParentDevToolsInspectorSidebar extends BaseDevToolsPanel {
+  constructor(context, panelOptions) {
+    super(context, panelOptions);
 
-    this.extension = context.extension;
     this.visible = false;
     this.destroyed = false;
 
-    this.toolbox = toolbox;
-    this.context = context;
-    this.sidebarOptions = sidebarOptions;
-
     this.context.callOnClose(this);
 
-    this.id = this.sidebarOptions.id;
     this.onSidebarSelect = this.onSidebarSelect.bind(this);
     this.onSidebarCreated = this.onSidebarCreated.bind(this);
+    this.onExtensionPageMount = this.onExtensionPageMount.bind(this);
+    this.onExtensionPageUnmount = this.onExtensionPageUnmount.bind(this);
+    this.onToolboxHostWillChange = this.onToolboxHostWillChange.bind(this);
+    this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
 
     this.toolbox.once(`extension-sidebar-created-${this.id}`, this.onSidebarCreated);
-    this.toolbox.on(`inspector-sidebar-select`, this.onSidebarSelect);
+    this.toolbox.on("inspector-sidebar-select", this.onSidebarSelect);
+    this.toolbox.on("host-will-change", this.onToolboxHostWillChange);
+    this.toolbox.on("host-changed", this.onToolboxHostChanged);
 
     // Set by setObject if the sidebar has not been created yet.
     this._initializeSidebar = null;
 
     // Set by _updateLastObjectValueGrip to keep track of the last
     // object value grip (to release the previous selected actor
     // on the remote debugging server when the actor changes).
     this._lastObjectValueGrip = null;
 
     this.toolbox.registerInspectorExtensionSidebar(this.id, {
-      title: sidebarOptions.title,
+      title: panelOptions.title,
     });
   }
 
   close() {
     if (this.destroyed) {
       throw new Error("Unable to close a destroyed DevToolsSelectionObserver");
     }
 
+    if (this.extensionSidebar) {
+      this.extensionSidebar.off("extension-page-mount", this.onExtensionPageMount);
+      this.extensionSidebar.off("extension-page-unmount", this.onExtensionPageUnmount);
+    }
+
+    if (this.browser) {
+      this.destroyBrowserElement();
+      this.browser = null;
+      this.containerEl = null;
+    }
+
     // Release the last selected actor on the remote debugging server.
     this._updateLastObjectValueGrip(null);
 
     this.toolbox.off(`extension-sidebar-created-${this.id}`, this.onSidebarCreated);
-    this.toolbox.off(`inspector-sidebar-select`, this.onSidebarSelect);
+    this.toolbox.off("inspector-sidebar-select", this.onSidebarSelect);
+    this.toolbox.off("host-changed", this.onToolboxHostChanged);
+    this.toolbox.off("host-will-change", this.onToolboxHostWillChange);
 
     this.toolbox.unregisterInspectorExtensionSidebar(this.id);
     this.extensionSidebar = null;
     this._lazySidebarInit = null;
 
     this.destroyed = true;
   }
 
+  onToolboxHostWillChange() {
+    if (this.browser) {
+      this.destroyBrowserElement();
+    }
+  }
+
+  onToolboxHostChanged() {
+    if (this.containerEl && this.panelOptions.url) {
+      this.createBrowserElement(this.containerEl.contentWindow);
+    }
+  }
+
+  onExtensionPageMount(containerEl) {
+    this.containerEl = containerEl;
+
+    // Wait the webext-panel.xul page to have been loaded in the
+    // inspector sidebar panel.
+    promiseDocumentLoaded(containerEl.contentDocument).then(() => {
+      this.createBrowserElement(containerEl.contentWindow);
+    });
+  }
+
+  onExtensionPageUnmount() {
+    this.containerEl = null;
+    this.destroyBrowserElement();
+  }
+
   onSidebarCreated(sidebar) {
     this.extensionSidebar = sidebar;
 
+    sidebar.on("extension-page-mount", this.onExtensionPageMount);
+    sidebar.on("extension-page-unmount", this.onExtensionPageUnmount);
+
     const {_lazySidebarInit} = this;
     this._lazySidebarInit = null;
 
     if (typeof _lazySidebarInit === "function") {
       _lazySidebarInit();
     }
   }
 
   onSidebarSelect(id) {
     if (!this.extensionSidebar) {
       return;
     }
 
     if (!this.visible && id === this.id) {
-      // TODO: Wait for top level context if extension page
       this.visible = true;
       this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsInspectorSidebarShown", {
         inspectorSidebarId: this.id,
       });
     } else if (this.visible && id !== this.id) {
       this.visible = false;
       this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsInspectorSidebarHidden", {
         inspectorSidebarId: this.id,
       });
     }
   }
 
+  setPage(extensionPageURL) {
+    this.panelOptions.url = extensionPageURL;
+
+    if (this.extensionSidebar) {
+      if (this.browser) {
+        // Just load the new extension page url in the existing browser, if
+        // it already exists.
+        this.browser.loadURI(this.panelOptions.url, {
+          triggeringPrincipal: this.context.extension.principal,
+        });
+      } else {
+        // The browser element doesn't exist yet, but the sidebar has been
+        // already created (e.g. because the inspector was already selected
+        // in a open toolbox and the extension has been installed/reloaded/updated).
+        this.extensionSidebar.setExtensionPage(WEBEXT_PANELS_URL);
+      }
+    } else {
+      // Defer the sidebar.setExtensionPage call.
+      this._setLazySidebarInit(
+        () => this.extensionSidebar.setExtensionPage(WEBEXT_PANELS_URL));
+    }
+  }
+
   setObject(object, rootTitle) {
+    delete this.panelOptions.url;
+
     this._updateLastObjectValueGrip(null);
 
     // Nest the object inside an object, as the value of the `rootTitle` property.
     if (rootTitle) {
       object = {[rootTitle]: object};
     }
 
     if (this.extensionSidebar) {
@@ -463,16 +500,18 @@ class ParentDevToolsInspectorSidebar {
     }
   }
 
   _setLazySidebarInit(cb) {
     this._lazySidebarInit = cb;
   }
 
   setObjectValueGrip(objectValueGrip, rootTitle) {
+    delete this.panelOptions.url;
+
     this._updateLastObjectValueGrip(objectValueGrip);
 
     if (this.extensionSidebar) {
       this.extensionSidebar.setObjectValueGrip(objectValueGrip, rootTitle);
     } else {
       // Defer the sidebar.setObjectValueGrip call.
       this._setLazySidebarInit(() => {
         this.extensionSidebar.setObjectValueGrip(objectValueGrip, rootTitle);
@@ -553,16 +592,20 @@ this.devtools_panels = class extends Ext
               // where it will be used to identify the messages related
               // to the panel API onShown/onHidden events.
               return Promise.resolve(id);
             },
             // The following methods are used internally to allow the sidebar API
             // piece that is running in the child process to asks the parent process
             // to execute the sidebar methods.
             Sidebar: {
+              setPage(sidebarId, extensionPageURL) {
+                const sidebar = sidebarsById.get(sidebarId);
+                return sidebar.setPage(extensionPageURL);
+              },
               setObject(sidebarId, jsonObject, rootTitle) {
                 const sidebar = sidebarsById.get(sidebarId);
                 return sidebar.setObject(jsonObject, rootTitle);
               },
               async setExpression(sidebarId, evalExpression, rootTitle) {
                 const sidebar = sidebarsById.get(sidebarId);
 
                 if (!waitForInspectedWindowFront) {
--- a/browser/components/extensions/schemas/devtools_panels.json
+++ b/browser/components/extensions/schemas/devtools_panels.json
@@ -225,23 +225,23 @@
                 "type": "function",
                 "optional": true,
                 "description": "A callback invoked after the sidebar is updated with the object."
               }
             ]
           },
           {
             "name": "setPage",
-            "unsupported": true,
             "type": "function",
+            "async": true,
             "description": "Sets an HTML page to be displayed in the sidebar pane.",
             "parameters": [
               {
                 "name": "path",
-                "type": "string",
+                "$ref": "manifest.ExtensionURL",
                 "description": "Relative path of an extension page to display within the sidebar."
               }
             ]
           }
         ],
         "events": [
           {
             "name": "onShown",
--- a/browser/components/extensions/test/browser/browser_ext_devtools_panels_elements_sidebar.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panels_elements_sidebar.js
@@ -58,62 +58,82 @@ async function testSidebarPanelSelect(ex
 
 add_task(async function test_devtools_panels_elements_sidebar() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
 
   async function devtools_page() {
     const sidebar1 = await browser.devtools.panels.elements.createSidebarPane("Test Sidebar 1");
     const sidebar2 = await browser.devtools.panels.elements.createSidebarPane("Test Sidebar 2");
     const sidebar3 = await browser.devtools.panels.elements.createSidebarPane("Test Sidebar 3");
+    const sidebar4 = await browser.devtools.panels.elements.createSidebarPane("Test Sidebar 4");
 
     const onShownListener = (event, sidebarInstance) => {
       browser.test.sendMessage(`devtools_sidebar_${event}`, sidebarInstance);
     };
 
     sidebar1.onShown.addListener(() => onShownListener("shown", "sidebar1"));
     sidebar2.onShown.addListener(() => onShownListener("shown", "sidebar2"));
     sidebar3.onShown.addListener(() => onShownListener("shown", "sidebar3"));
+    sidebar4.onShown.addListener(() => onShownListener("shown", "sidebar4"));
 
     sidebar1.onHidden.addListener(() => onShownListener("hidden", "sidebar1"));
     sidebar2.onHidden.addListener(() => onShownListener("hidden", "sidebar2"));
     sidebar3.onHidden.addListener(() => onShownListener("hidden", "sidebar3"));
+    sidebar4.onHidden.addListener(() => onShownListener("hidden", "sidebar4"));
 
     // Refresh the sidebar content on every inspector selection.
     browser.devtools.panels.elements.onSelectionChanged.addListener(() => {
       const expression = `
         var obj = Object.create(null);
         obj.prop1 = 123;
         obj[Symbol('sym1')] = 456;
         obj.cyclic = obj;
         obj;
       `;
       sidebar1.setExpression(expression, "sidebar.setExpression rootTitle");
     });
 
     sidebar2.setObject({anotherPropertyName: 123});
     sidebar3.setObject({propertyName: "propertyValue"}, "Optional Root Object Title");
 
+    sidebar4.setPage("sidebar.html");
+
     browser.test.sendMessage("devtools_page_loaded");
   }
 
+  function sidebar() {
+    browser.test.sendMessage("sidebar-loaded");
+  }
+
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       devtools_page: "devtools_page.html",
     },
     files: {
       "devtools_page.html": `<!DOCTYPE html>
       <html>
        <head>
          <meta charset="utf-8">
        </head>
        <body>
          <script src="devtools_page.js"></script>
        </body>
       </html>`,
       "devtools_page.js": devtools_page,
+      "sidebar.html": `<!DOCTYPE html>
+      <html>
+       <head>
+         <meta charset="utf-8">
+       </head>
+       <body>
+         sidebar panel
+         <script src="sidebar.js"></script>
+       </body>
+      </html>`,
+      "sidebar.js": sidebar,
     },
   });
 
   await extension.startup();
 
   let target = await devtools.TargetFactory.forTab(tab);
 
   const toolbox = await gDevTools.showToolbox(target, "webconsole");
@@ -182,30 +202,46 @@ add_task(async function test_devtools_pa
   const sidebarPanel3 = inspector.sidebar.getTabPanel(sidebarIds[2]);
 
   ok(sidebarPanel3, "Got a rendered sidebar panel for the third registered extension sidebar");
 
   testSetObjectSidebarPanel(sidebarPanel3, "string", "Optional Root Object Title");
 
   info("Unloading the extension and check that all the sidebar have been removed");
 
+  inspector.sidebar.show(sidebarIds[3]);
+
+  const shownSidebarInstance4 = await extension.awaitMessage("devtools_sidebar_shown");
+  const hiddenSidebarInstance3 = await extension.awaitMessage("devtools_sidebar_hidden");
+
+  is(shownSidebarInstance4, "sidebar4", "Got the shown event on the third extension sidebar");
+  is(hiddenSidebarInstance3, "sidebar3", "Got the hidden event on the second extension sidebar");
+
+  isActiveSidebarTabTitle(inspector, "Test Sidebar 4",
+                          "Got the expected title on the active sidebar tab");
+
+  await extension.awaitMessage("sidebar-loaded");
+
   await extension.unload();
 
   is(Array.from(toolbox._inspectorExtensionSidebars.keys()).length, 0,
      "All the registered sidebars have been unregistered on extension unload");
 
   is(inspector.sidebar.getTabPanel(sidebarIds[0]), undefined,
      "The first registered sidebar has been removed");
 
   is(inspector.sidebar.getTabPanel(sidebarIds[1]), undefined,
      "The second registered sidebar has been removed");
 
   is(inspector.sidebar.getTabPanel(sidebarIds[2]), undefined,
      "The third registered sidebar has been removed");
 
+  is(inspector.sidebar.getTabPanel(sidebarIds[3]), undefined,
+     "The third registered sidebar has been removed");
+
   await expectNoSuchActorIDs(target.client, actors);
 
   await gDevTools.closeToolbox(target);
 
   await target.destroy();
 
   BrowserTestUtils.removeTab(tab);
 });
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -163,26 +163,30 @@ class UrlbarInput {
   }
 
   /**
    * Passes DOM events for the textbox to the _on<event type> methods.
    * @param {Event} event
    *   DOM event from the <textbox>.
    */
   handleEvent(event) {
-    let methodName = "_on" + event.type;
+    let methodName = "_on_" + event.type;
     if (methodName in this) {
       this[methodName](event);
     } else {
       throw "Unrecognized urlbar event: " + event.type;
     }
   }
 
   // Getters and Setters below.
 
+  _get_focused() {
+    return this.inputField.getAttribute("focused") == "true";
+  }
+
   _set_value(val) {
     val = this.trimValue(val);
 
     this.valueIsTyped = false;
     this.inputField.value = val;
 
     return val;
   }
@@ -314,87 +318,87 @@ class UrlbarInput {
       }
     }
 
     return action;
   }
 
   // Event handlers below.
 
-  _onblur(event) {
+  _on_blur(event) {
     this.formatValue();
   }
 
-  _onfocus(event) {
+  _on_focus(event) {
     this.formatValue();
   }
 
-  _onmousedown(event) {
+  _on_mousedown(event) {
     if (event.button == 0 &&
         event.detail == 2 &&
         UrlbarPrefs.get("doubleClickSelectsAll")) {
       this.editor.selectAll();
       event.preventDefault();
     }
   }
 
-  _oninput(event) {
+  _on_input(event) {
     this.valueIsTyped = true;
 
     // XXX Fill in lastKey, and add anything else we need.
     this.controller.startQuery(new QueryContext({
       searchString: event.target.value,
       lastKey: "",
       maxResults: UrlbarPrefs.get("maxRichResults"),
       isPrivate: this.isPrivate,
     }));
   }
 
-  _onselect(event) {
+  _on_select(event) {
     if (!Services.clipboard.supportsSelectionClipboard()) {
       return;
     }
 
     if (!this.window.windowUtils.isHandlingUserInput) {
       return;
     }
 
     let val = this._getSelectedValueForClipboard();
     if (!val) {
       return;
     }
 
     ClipboardHelper.copyStringToClipboard(val, Services.clipboard.kSelectionClipboard);
   }
 
-  _onoverflow(event) {
+  _on_overflow(event) {
     const targetIsPlaceholder =
       !event.originalTarget.classList.contains("anonymous-div");
     // We only care about the non-placeholder text.
     // This shouldn't be needed, see bug 1487036.
     if (targetIsPlaceholder) {
       return;
     }
     this._inOverflow = true;
     this._updateTextOverflow();
   }
 
-  _onunderflow(event) {
+  _on_underflow(event) {
     const targetIsPlaceholder =
       !event.originalTarget.classList.contains("anonymous-div");
     // We only care about the non-placeholder text.
     // This shouldn't be needed, see bug 1487036.
     if (targetIsPlaceholder) {
       return;
     }
     this._inOverflow = false;
     this._updateTextOverflow();
   }
 
-  _onscrollend(event) {
+  _on_scrollend(event) {
     this._updateTextOverflow();
   }
 }
 
 /**
  * Handles copy and cut commands for the urlbar.
  */
 class CopyCutController {
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -34,30 +34,30 @@ a tab (i.e. it is a verb, not adjective)
 <!-- LOCALIZATION NOTE (closeTabsToTheEnd.label): This should indicate the
 direction in which tabs are closed, i.e. locales that use RTL mode should say
 left instead of right. -->
 <!ENTITY  closeTabsToTheEnd.label            "Close Tabs to the Right">
 <!ENTITY  closeTabsToTheEnd.accesskey        "i">
 <!ENTITY  closeOtherTabs.label               "Close Other Tabs">
 <!ENTITY  closeOtherTabs.accesskey           "o">
 
-<!ENTITY  closeSelectedTabs.label            "Close Selected Tabs">
-<!ENTITY  closeSelectedTabs.accesskey        "S">
+<!ENTITY  closeTabs.label                    "Close Tabs">
+<!ENTITY  closeTabs.accesskey                "S">
 <!ENTITY  pinSelectedTabs.label              "Pin Tabs">
 <!-- Pin Tab and Pin Selected Tabs have the same accesskey
 but will never be visible at the same time. -->
 <!ENTITY  pinSelectedTabs.accesskey          "P">
 <!ENTITY  unpinSelectedTabs.label            "Unpin Tabs">
 <!-- Unpin Tab and Unpin Selected Tabs have the same accesskey
 but will never be visible at the same time. -->
 <!ENTITY  unpinSelectedTabs.accesskey        "b">
-<!-- Reload Tab and Reload Selected Tabs have the same accesskey
+<!-- LOCALIZATION NOTE(reloadTab.label, reloadTabs.label): have the same accesskey
 but will never be visible at the same time. -->
-<!ENTITY  reloadSelectedTabs.label           "Reload Selected Tabs">
-<!ENTITY  reloadSelectedTabs.accesskey       "R">
+<!ENTITY  reloadTabs.label                   "Reload Tabs">
+<!ENTITY  reloadTabs.accesskey               "R">
 <!ENTITY  bookmarkSelectedTabs.label         "Bookmark Tabs…">
 <!ENTITY  bookmarkSelectedTabs.accesskey     "k">
 
 <!-- LOCALIZATION NOTE (pinTab.label, unpinTab.label): "Pin" is being
 used as a metaphor for expressing the fact that these tabs are "pinned" to the
 left edge of the tabstrip. Really we just want the string to express the idea
 that this is a lightweight and reversible action that keeps your tab where you
 can reach it easily. -->
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -157,16 +157,22 @@
 /* Override theme colors since the picker uses extra colors that
    themes cannot set */
 #DateTimePickerPanel[active="true"] {
   --arrowpanel-background: var(--default-arrowpanel-background);
   --arrowpanel-color: var(--default-arrowpanel-color);
   --arrowpanel-border-color: var(--default-arrowpanel-border-color);
 }
 
+#DateTimePickerPanel[side="top"],
+#DateTimePickerPanel[side="bottom"] {
+  margin-left: 0;
+  margin-right: 0;
+}
+
 #widget-overflow .webextension-popup-browser {
   background: #fff;
 }
 
 /* Contextual Feature Recommendation popup-notification */
 
 :root {
   --cfr-notification-header-image: url(resource://activity-stream/data/content/assets/glyph-help-24.svg);
--- a/build/docs/tup.rst
+++ b/build/docs/tup.rst
@@ -43,16 +43,24 @@ Configuration
 =============
 
 Your mozconfig needs to describe how to find the executable if it's not in your
 PATH, and enable the Tup backend::
 
    export TUP=~/.mozbuild/tup/tup
    ac_add_options --enable-build-backends=Tup
 
+Configuring Parallel Jobs
+-------------------------
+
+To override the default number of jobs run in parallel, set MOZ_PARALLEL_BUILD
+in your mozconfig::
+
+    mk_add_options MOZ_PARALLEL_BUILD=8
+
 What Works
 ==========
 
 You should expect a Linux desktop build to generate a working Firefox binary
 from a ``./mach build``, and be able to run test suites against it (eg:
 mochitests, xpcshell, gtest). Top-level incremental builds should be fast
 enough to use them during a regular compile/edit/test cycle. If you wish to
 stop compilation partway through the build to more quickly iterate on a
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1623,16 +1623,23 @@ Toolbox.prototype = {
 
   /**
    * Unregister an extension sidebar for the inspector panel.
    *
    * @param {String} id
    *        An unique sidebar id
    */
   unregisterInspectorExtensionSidebar(id) {
+    // Unregister the sidebar from the toolbox if the toolbox is not already
+    // being destroyed (otherwise we would trigger a re-rendering of the
+    // inspector sidebar tabs while the toolbox is going away).
+    if (this._destroyer) {
+      return;
+    }
+
     const sidebarDef = this._inspectorExtensionSidebars.get(id);
     if (!sidebarDef) {
       return;
     }
 
     this._inspectorExtensionSidebars.delete(id);
 
     // Remove the created sidebar instance if the inspector panel
@@ -2806,16 +2813,22 @@ Toolbox.prototype = {
    */
   destroy: function() {
     // If several things call destroy then we give them all the same
     // destruction promise so we're sure to destroy only once
     if (this._destroyer) {
       return this._destroyer;
     }
 
+    this._destroyer = this._destroyToolbox();
+
+    return this._destroyer;
+  },
+
+  _destroyToolbox: async function() {
     this.emit("destroy");
 
     this._target.off("inspect-object", this._onInspectObject);
     this._target.off("will-navigate", this._onWillNavigate);
     this._target.off("navigate", this._refreshHostTitle);
     this._target.off("frame-update", this._updateFrames);
     this.off("select", this._onToolSelected);
     this.off("host-changed", this._refreshHostTitle);
@@ -2924,17 +2937,17 @@ Toolbox.prototype = {
       "host": host,
       "width": width,
       "session_id": this.sessionId
     });
 
     // Finish all outstanding tasks (which means finish destroying panels and
     // then destroying the host, successfully or not) before destroying the
     // target.
-    this._destroyer = new Promise(resolve => {
+    const onceDestroyed = new Promise(resolve => {
       resolve(settleAll(outstanding)
         .catch(console.error)
         .then(() => {
           const api = this._netMonitorAPI;
           this._netMonitorAPI = null;
           return api ? api.destroy() : null;
         }, console.error)
         .then(() => {
@@ -2980,26 +2993,25 @@ Toolbox.prototype = {
             win.windowUtils.garbageCollect();
           }
         }).catch(console.error));
     });
 
     const leakCheckObserver = ({wrappedJSObject: barrier}) => {
       // Make the leak detector wait until this toolbox is properly destroyed.
       barrier.client.addBlocker("DevTools: Wait until toolbox is destroyed",
-                                this._destroyer);
+                                onceDestroyed);
     };
 
     const topic = "shutdown-leaks-before-check";
     Services.obs.addObserver(leakCheckObserver, topic);
-    this._destroyer.then(() => {
-      Services.obs.removeObserver(leakCheckObserver, topic);
-    });
-
-    return this._destroyer;
+
+    await onceDestroyed;
+
+    Services.obs.removeObserver(leakCheckObserver, topic);
   },
 
   _highlighterReady: function() {
     this.emit("highlighter-ready");
   },
 
   _highlighterHidden: function() {
     this.emit("highlighter-hide");
--- a/devtools/client/inspector/extensions/actions/index.js
+++ b/devtools/client/inspector/extensions/actions/index.js
@@ -9,12 +9,15 @@ const { createEnum } = require("devtools
 createEnum([
 
   // Update the extension sidebar with an object TreeView.
   "EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE",
 
   // Update the extension sidebar with an object value grip preview.
   "EXTENSION_SIDEBAR_OBJECT_GRIP_VIEW_UPDATE",
 
+  // Switch the extension sidebar into an extension page container.
+  "EXTENSION_SIDEBAR_PAGE_UPDATE",
+
   // Remove an extension sidebar from the inspector store.
   "EXTENSION_SIDEBAR_REMOVE"
 
 ], module.exports);
--- a/devtools/client/inspector/extensions/actions/sidebar.js
+++ b/devtools/client/inspector/extensions/actions/sidebar.js
@@ -2,16 +2,17 @@
  * 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 {
   EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE,
   EXTENSION_SIDEBAR_OBJECT_GRIP_VIEW_UPDATE,
+  EXTENSION_SIDEBAR_PAGE_UPDATE,
   EXTENSION_SIDEBAR_REMOVE,
 } = require("./index");
 
 module.exports = {
 
   /**
    * Update the sidebar with an object treeview.
    */
@@ -31,16 +32,27 @@ module.exports = {
       type: EXTENSION_SIDEBAR_OBJECT_GRIP_VIEW_UPDATE,
       sidebarId,
       objectValueGrip,
       rootTitle,
     };
   },
 
   /**
+   * Switch the sidebar into the extension page mode.
+   */
+  updateExtensionPage(sidebarId, iframeURL) {
+    return {
+      type: EXTENSION_SIDEBAR_PAGE_UPDATE,
+      sidebarId,
+      iframeURL,
+    };
+  },
+
+  /**
    * Remove the extension sidebar from the inspector store.
    */
   removeExtensionSidebar(sidebarId) {
     return {
       type: EXTENSION_SIDEBAR_REMOVE,
       sidebarId,
     };
   }
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/components/ExtensionPage.js
@@ -0,0 +1,53 @@
+/* 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 { createRef, 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");
+
+/**
+ * The ExtensionPage React Component is used in the ExtensionSidebar component to provide
+ * a UI viewMode which shows an extension page rendered inside the sidebar panel.
+ */
+class ExtensionPage extends PureComponent {
+  static get propTypes() {
+    return {
+      iframeURL: PropTypes.string.isRequired,
+      onExtensionPageMount: PropTypes.func.isRequired,
+      onExtensionPageUnmount: PropTypes.func.isRequired,
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.iframeRef = createRef();
+  }
+
+  componentDidMount() {
+    this.props.onExtensionPageMount(this.iframeRef.current);
+  }
+
+  componentWillUnmount() {
+    this.props.onExtensionPageUnmount(this.iframeRef.current);
+  }
+
+  render() {
+    return dom.iframe({
+      className: "inspector-extension-sidebar-page",
+      src: this.props.iframeURL,
+      style: {
+        width: "100%",
+        height: "100%",
+        margin: 0,
+        padding: 0,
+      },
+      ref: this.iframeRef,
+    });
+  }
+}
+
+module.exports = ExtensionPage;
--- a/devtools/client/inspector/extensions/components/ExtensionSidebar.js
+++ b/devtools/client/inspector/extensions/components/ExtensionSidebar.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 const { createFactory, 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 { connect } = require("devtools/client/shared/vendor/react-redux");
 
+const ExtensionPage = createFactory(require("./ExtensionPage"));
 const ObjectTreeView = createFactory(require("./ObjectTreeView"));
 const ObjectValueGripView = createFactory(require("./ObjectValueGripView"));
 const Types = require("../types");
 
 /**
  * The ExtensionSidebar is a React component with 2 supported viewMode:
  * - an ObjectTreeView UI, used to show the JS objects
  *   (used by the sidebar.setObject WebExtensions APIs)
@@ -24,53 +25,71 @@ const Types = require("../types");
  *
  * TODO: implement the ExtensionPage viewMode.
  */
 class ExtensionSidebar extends PureComponent {
   static get propTypes() {
     return {
       id: PropTypes.string.isRequired,
       extensionsSidebar: PropTypes.object.isRequired,
+      onExtensionPageMount: PropTypes.func.isRequired,
+      onExtensionPageUnmount: PropTypes.func.isRequired,
       // Helpers injected as props by extension-sidebar.js.
       serviceContainer: PropTypes.shape(Types.serviceContainer).isRequired,
     };
   }
 
   render() {
     const {
       id,
       extensionsSidebar,
+      onExtensionPageMount,
+      onExtensionPageUnmount,
       serviceContainer,
     } = this.props;
 
     const {
-      viewMode = "empty-sidebar",
+      iframeURL,
       object,
       objectValueGrip,
-      rootTitle
+      rootTitle,
+      viewMode = "empty-sidebar",
     } = extensionsSidebar[id] || {};
 
     let sidebarContentEl;
 
     switch (viewMode) {
       case "object-treeview":
         sidebarContentEl = ObjectTreeView({ object });
         break;
       case "object-value-grip-view":
         sidebarContentEl = ObjectValueGripView({
           objectValueGrip,
+          rootTitle,
           serviceContainer,
-          rootTitle,
+        });
+        break;
+      case "extension-page":
+        sidebarContentEl = ExtensionPage({
+          iframeURL,
+          onExtensionPageMount,
+          onExtensionPageUnmount,
         });
         break;
       case "empty-sidebar":
         break;
       default:
         throw new Error(`Unknown ExtensionSidebar viewMode: "${viewMode}"`);
     }
 
     const className = "devtools-monospace extension-sidebar inspector-tabpanel";
 
-    return dom.div({ id, className }, sidebarContentEl);
+    return dom.div({
+      id,
+      className,
+      style: {
+        height: "100%",
+      },
+    }, sidebarContentEl);
   }
 }
 
 module.exports = connect(state => state)(ExtensionSidebar);
--- a/devtools/client/inspector/extensions/components/moz.build
+++ b/devtools/client/inspector/extensions/components/moz.build
@@ -1,11 +1,12 @@
 # -*- 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/.
 
 DevToolsModules(
+    'ExtensionPage.js',
     'ExtensionSidebar.js',
     'ObjectTreeView.js',
     'ObjectValueGripView.js',
 )
--- a/devtools/client/inspector/extensions/extension-sidebar.js
+++ b/devtools/client/inspector/extensions/extension-sidebar.js
@@ -1,21 +1,22 @@
 /* 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 { createElement, createFactory } = require("devtools/client/shared/vendor/react");
+const EventEmitter = require("devtools/shared/event-emitter");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
-
 const ObjectClient = require("devtools/shared/client/object-client");
 const ExtensionSidebarComponent = createFactory(require("./components/ExtensionSidebar"));
 
 const {
+  updateExtensionPage,
   updateObjectTreeView,
   updateObjectValueGripView,
   removeExtensionSidebar,
 } = require("./actions/sidebar");
 
 /**
  * ExtensionSidebar instances represents Inspector sidebars installed by add-ons
  * using the devtools.panels.elements.createSidebarPane WebExtensions API.
@@ -30,16 +31,17 @@ const {
  * @param {Object} options
  * @param {String} options.id
  *        The unique id of the sidebar.
  * @param {String} options.title
  *        The title of the sidebar.
  */
 class ExtensionSidebar {
   constructor(inspector, {id, title}) {
+    EventEmitter.decorate(this);
     this.inspector = inspector;
     this.store = inspector.store;
     this.id = id;
     this.title = title;
 
     this.destroyed = false;
   }
 
@@ -49,16 +51,22 @@ class ExtensionSidebar {
   get provider() {
     if (!this._provider) {
       this._provider = createElement(Provider, {
         store: this.store,
         key: this.id,
         title: this.title,
       }, ExtensionSidebarComponent({
         id: this.id,
+        onExtensionPageMount: (containerEl) => {
+          this.emit("extension-page-mount", containerEl);
+        },
+        onExtensionPageUnmount: (containerEl) => {
+          this.emit("extension-page-unmount", containerEl);
+        },
         serviceContainer: {
           createObjectClient: (object) => {
             return new ObjectClient(this.inspector.toolbox.target.client, object);
           },
           releaseActor: (actor) => {
             if (!actor) {
               return;
             }
@@ -147,11 +155,19 @@ class ExtensionSidebar {
    */
   setObjectValueGrip(objectValueGrip, rootTitle) {
     if (this.removed) {
       throw new Error("Unable to set an object preview on a removed ExtensionSidebar");
     }
 
     this.store.dispatch(updateObjectValueGripView(this.id, objectValueGrip, rootTitle));
   }
+
+  setExtensionPage(iframeURL) {
+    if (this.removed) {
+      throw new Error("Unable to set an object preview on a removed ExtensionSidebar");
+    }
+
+    this.store.dispatch(updateExtensionPage(this.id, iframeURL));
+  }
 }
 
 module.exports = ExtensionSidebar;
--- a/devtools/client/inspector/extensions/reducers/sidebar.js
+++ b/devtools/client/inspector/extensions/reducers/sidebar.js
@@ -2,16 +2,17 @@
  * 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 {
   EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE,
   EXTENSION_SIDEBAR_OBJECT_GRIP_VIEW_UPDATE,
+  EXTENSION_SIDEBAR_PAGE_UPDATE,
   EXTENSION_SIDEBAR_REMOVE,
 } = require("../actions/index");
 
 const INITIAL_SIDEBAR = {};
 
 const reducers = {
 
   [EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE](sidebar, {sidebarId, object}) {
@@ -34,16 +35,27 @@ const reducers = {
       [sidebarId]: {
         viewMode: "object-value-grip-view",
         objectValueGrip,
         rootTitle,
       }
     });
   },
 
+  [EXTENSION_SIDEBAR_PAGE_UPDATE](sidebar, {sidebarId, iframeURL}) {
+    // Update the sidebar to a "object-treeview" which shows
+    // the passed object.
+    return Object.assign({}, sidebar, {
+      [sidebarId]: {
+        viewMode: "extension-page",
+        iframeURL,
+      }
+    });
+  },
+
   [EXTENSION_SIDEBAR_REMOVE](sidebar, {sidebarId}) {
     // Remove the sidebar from the Redux store.
     delete sidebar[sidebarId];
     return Object.assign({}, sidebar);
   },
 
 };
 
--- a/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
+++ b/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
@@ -223,16 +223,33 @@ add_task(async function testSidebarDOMNo
   nodeFront = await onceNewNodeFront;
   is(nodeFront.displayName, "body", "The correct node has been selected");
   nodeFront = await onNodeHighlight;
   is(nodeFront.displayName, "body", "The correct node was highlighted");
 
   await onNodeUnhighlight;
 });
 
+add_task(async function testSidebarSetExtensionPage() {
+  const inspectedWindowFront = toolbox.target.getFront("webExtensionInspectedWindow");
+
+  const sidebar = inspector.getPanel(SIDEBAR_ID);
+  const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+  info("Testing sidebar.setExtensionPage");
+
+  const expectedURL = "data:text/html,<!DOCTYPE html><html><body><h1>Extension Page";
+
+  sidebar.setExtensionPage(expectedURL);
+
+  await testSetExtensionPageSidebarPanel(sidebarPanelContent, expectedURL);
+
+  inspectedWindowFront.destroy();
+});
+
 add_task(async function teardownExtensionSidebar() {
   info("Remove the sidebar instance");
 
   toolbox.unregisterInspectorExtensionSidebar(SIDEBAR_ID);
 
   ok(!inspector.sidebar.getTabPanel(SIDEBAR_ID),
      "The rendered extension sidebar has been removed");
 
@@ -244,8 +261,34 @@ add_task(async function teardownExtensio
   await toolbox.destroy();
 
   await extension.unload();
 
   toolbox = null;
   inspector = null;
   extension = null;
 });
+
+add_task(async function testActiveTabOnNonExistingSidebar() {
+  // Set a fake non existing sidebar id in the activeSidebar pref,
+  // to simulate the scenario where an extension has installed a sidebar
+  // which has been saved in the preference but it doesn't exist anymore.
+  await SpecialPowers.pushPrefEnv({
+    set: [["devtools.inspector.activeSidebar"], "unexisting-sidebar-id"],
+  });
+
+  const res = await openInspectorForURL("about:blank");
+  inspector = res.inspector;
+  toolbox = res.toolbox;
+
+  const onceSidebarCreated = toolbox.once(`extension-sidebar-created-${SIDEBAR_ID}`);
+  toolbox.registerInspectorExtensionSidebar(SIDEBAR_ID, {title: SIDEBAR_TITLE});
+
+  // Wait the extension sidebar to be created and then unregister it to force the tabbar
+  // to select a new one.
+  await onceSidebarCreated;
+  toolbox.unregisterInspectorExtensionSidebar(SIDEBAR_ID);
+
+  is(inspector.sidebar.getCurrentTabID(), "layoutview",
+     "Got the expected inspector sidebar tab selected");
+
+  await SpecialPowers.popPrefEnv();
+});
--- a/devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js
+++ b/devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js
@@ -46,16 +46,34 @@ async function expectNoSuchActorIDs(clie
 
 function waitForObjectInspector(panelDoc, waitForNodeWithType = "object") {
   const selector = `.object-inspector .objectBox-${waitForNodeWithType}`;
   return ContentTaskUtils.waitForCondition(() => {
     return panelDoc.querySelectorAll(selector).length > 0;
   });
 }
 
+// Helper function used inside the sidebar.setExtensionPage test case.
+async function testSetExtensionPageSidebarPanel(panelDoc, expectedURL) {
+  const selector = "iframe.inspector-extension-sidebar-page";
+  const iframesCount = await ContentTaskUtils.waitForCondition(() => {
+    return panelDoc.querySelectorAll(selector).length > 0;
+  }, "Wait for the extension page iframe");
+
+  is(iframesCount, 1, "Got the expected number of iframes in the extension panel");
+
+  const iframeWindow = panelDoc.querySelector(selector).contentWindow;
+  await ContentTaskUtils.waitForCondition(() => {
+    return iframeWindow.document.readyState === "complete";
+  }, "Wait for the extension page iframe to complete to load");
+
+  is(iframeWindow.location.href, expectedURL,
+     "Got the expected url in the extension panel iframe");
+}
+
 // Helper function used inside the sidebar.setObjectValueGrip test case.
 async function testSetExpressionSidebarPanel(panel, expected) {
   const {
     nodesLength,
     propertiesNames,
     rootTitle,
   } = expected;
 
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -868,16 +868,18 @@ Inspector.prototype = {
 
     this.sidebar = new ToolSidebar(sidebar, this, "inspector", options);
 
     const ruleSideBar = this.panelDoc.getElementById("inspector-rules-sidebar");
     this.ruleViewSideBar = new ToolSidebar(ruleSideBar, this, "inspector", {
       hideTabstripe: true
     });
 
+    // defaultTab may also be an empty string or a tab id that doesn't exist anymore
+    // (e.g. it was a tab registered by an addon that has been uninstalled).
     let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
 
     if (this.is3PaneModeEnabled && defaultTab === "ruleview") {
       defaultTab = "computedview";
     }
 
     // Append all side panels
 
--- a/devtools/client/shared/components/tabs/TabBar.js
+++ b/devtools/client/shared/components/tabs/TabBar.js
@@ -134,18 +134,20 @@ class Tabbar extends Component {
   }
 
   addAllQueuedTabs() {
     if (!this.queuedTabs.length) {
       return;
     }
 
     const tabs = this.state.tabs.slice();
-    let activeId;
-    let activeTab;
+
+    // Preselect the first sidebar tab if none was explicitly selected.
+    let activeTab = 0;
+    let activeId = this.queuedTabs[0].id;
 
     for (const { id, index, panel, selected, title, url } of this.queuedTabs) {
       if (index >= 0) {
         tabs.splice(index, 0, {id, title, panel, url});
       } else {
         tabs.push({id, title, panel, url});
       }
 
--- a/dom/plugins/test/mochitest/test_bug479979.xul
+++ b/dom/plugins/test/mochitest/test_bug479979.xul
@@ -8,18 +8,20 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript" src="plugin-utils.js"></script>
 <body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
 <script class="testbody" type="application/javascript">
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
 
-function runTests() {
+async function runTests() {
   var pluginElement1 = document.getElementById("plugin1");
+  await SimpleTest.promiseWaitForCondition(() => pluginElement1.setUndefinedValueTest !== undefined,
+                                           "Waited too long for plugin to activate");
 
   var rv = true; // we want !true from the test plugin
   var exceptionThrown = false;
   try {
     rv = pluginElement1.setUndefinedValueTest();
   } catch (e) {
     exceptionThrown = true;
   }
--- a/dom/plugins/test/mochitest/test_busy_hang.xul
+++ b/dom/plugins/test/mochitest/test_busy_hang.xul
@@ -17,31 +17,34 @@
   <body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
     <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
   </body>
   <script class="testbody" type="application/javascript">
     <![CDATA[
 SimpleTest.waitForExplicitFinish();
 SimpleTest.expectChildProcessCrash();
 
-function runTests() {
+async function runTests() {
   // Default plugin hang timeout is too high for mochitests
   var prefs = Cc["@mozilla.org/preferences-service;1"]
                     .getService(Ci.nsIPrefBranch);
   var timeoutPref = "dom.ipc.plugins.timeoutSecs";
   prefs.setIntPref(timeoutPref, 5);
 
   var os = Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService);
   os.addObserver(testObserver, "plugin-crashed", true);
 
   testObserver.idleHang = false;
   document.addEventListener("PluginCrashed", onPluginCrashed, false);
 
   var pluginElement = document.getElementById("plugin1");
+  await SimpleTest.promiseWaitForCondition(() => pluginElement.hang !== undefined,
+                                           "Waited too long for plugin to activate");
+
   try {
     pluginElement.hang(true);
   } catch (e) {
   }
 }
 ]]>
   </script>
 </window>
--- a/dom/plugins/test/mochitest/test_convertpoint.xul
+++ b/dom/plugins/test/mochitest/test_convertpoint.xul
@@ -12,18 +12,20 @@
   </script>
 <body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
 <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
 </body>
 <script class="testbody" type="application/javascript">
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 
-function runTests() {
+async function runTests() {
   var pluginElement = document.getElementById("plugin1");
+  await SimpleTest.promiseWaitForCondition(() => pluginElement.convertPointX !== undefined,
+                                           "Waited too long for plugin to activate");
   // Poll to see if the plugin is in the right place yet.
   // Check if x-coordinate 0 in plugin space is 0 in window space. If it is,
   // the plugin hasn't been placed yet.
   if (pluginElement.convertPointX(1, 0, 0, 2) == 0) {
     setTimeout(runTests, 0);
     return;
   }
 
--- a/dom/plugins/test/mochitest/test_crash_notify.xul
+++ b/dom/plugins/test/mochitest/test_crash_notify.xul
@@ -86,24 +86,26 @@ function onPluginCrashed(aEvent) {
 
   var os = Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService);
   os.removeObserver(testObserver, "plugin-crashed");
 
   eventListenerDeferred.resolve();
 }
 
-function runTests() {
+async function runTests() {
   var os = Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService);
   os.addObserver(testObserver, "plugin-crashed", true);
 
   document.addEventListener("PluginCrashed", onPluginCrashed, false);
 
   var pluginElement = document.getElementById("plugin1");
+  await SimpleTest.promiseWaitForCondition(() => pluginElement.crash !== undefined,
+                                           "Waited too long for plugin to activate");
   try {
     pluginElement.crash();
   } catch (e) {
   }
 
   Promise.all([
     observerDeferred.promise,
     eventListenerDeferred.promise
--- a/dom/plugins/test/mochitest/test_crash_notify_no_report.xul
+++ b/dom/plugins/test/mochitest/test_crash_notify_no_report.xul
@@ -91,31 +91,34 @@ function onPluginCrashed(aEvent) {
   let env = Cc["@mozilla.org/process/environment;1"]
               .getService(Ci.nsIEnvironment);
   env.set("MOZ_CRASHREPORTER_NO_REPORT", "1");
   Services.crashmanager.ensureCrashIsPresent(aEvent.pluginDumpID).then(() => {
     SimpleTest.finish();
   });
 }
 
-function runTests() {
+async function runTests() {
   // the test harness will have set MOZ_CRASHREPORTER_NO_REPORT,
   // ensure that we can change the setting and have our minidumps
   // wind up in Crash Reports/pending
   let env = Cc["@mozilla.org/process/environment;1"]
               .getService(Ci.nsIEnvironment);
   env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
 
   var os = Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService);
   os.addObserver(testObserver, "plugin-crashed", true);
 
   document.addEventListener("PluginCrashed", onPluginCrashed, false);
 
   var pluginElement = document.getElementById("plugin1");
+  await SimpleTest.promiseWaitForCondition(() => pluginElement.crash !== undefined,
+                                           "Waited too long for plugin to activate");
+
   try {
     pluginElement.crash();
   } catch (e) {
   }
 }
 ]]>
 </script>
 </window>
--- a/dom/plugins/test/mochitest/test_crash_submit.xul
+++ b/dom/plugins/test/mochitest/test_crash_submit.xul
@@ -142,16 +142,18 @@ function runTests() {
   var pluginElement = document.getElementById("plugin1");
   try {
     (async function() {
       // Clear data in CrashManager in case previous tests caused something
       // to be added.
       let store = await Services.crashmanager._getStore();
       store.reset();
 
+      await SimpleTest.promiseWaitForCondition(() => pluginElement.crash !== undefined,
+                                               "Waited too long for plugin to activate");
       pluginElement.crash();
     })();
   } catch (e) {
   }
 }
 ]]>
 </script>
 </window>
--- a/dom/plugins/test/mochitest/test_hang_submit.xul
+++ b/dom/plugins/test/mochitest/test_hang_submit.xul
@@ -121,17 +121,17 @@ function onPluginCrashed(aEvent) {
 
   let submitButton = document.getAnonymousElementByAttribute(aEvent.target,
                                                              "class",
                                                              "submitButton");
   // try to submit this report
   sendMouseEvent({type:'click'}, submitButton, window);
 }
 
-function runTests() {
+async function runTests() {
   // Default plugin hang timeout is too high for mochitests
   Services.prefs.setIntPref("dom.ipc.plugins.timeoutSecs", 1);
 
   // the test harness will have set MOZ_CRASHREPORTER_NO_REPORT,
   // ensure that we can change the setting and have our minidumps
   // wind up in Crash Reports/pending
   let env = Cc["@mozilla.org/process/environment;1"]
                       .getService(Ci.nsIEnvironment);
@@ -140,16 +140,18 @@ function runTests() {
   // Override the crash reporter URL to send to our fake server
   crashReporter.serverURL = NetUtil.newURI(SERVER_URL);
 
   // Hook into plugin crash events
   Services.obs.addObserver(testObserver, "crash-report-status", true);
   document.addEventListener("PluginCrashed", onPluginCrashed, false);
 
   var pluginElement = document.getElementById("plugin1");
+  await SimpleTest.promiseWaitForCondition(() => pluginElement.hang !== undefined,
+                                           "Waited too long for plugin to activate");
   try {
     (async function() {
       // Clear data in CrashManager in case previous tests caused something
       // to be added.
       let store = await Services.crashmanager._getStore();
       store.reset();
 
       pluginElement.hang();
--- a/dom/plugins/test/mochitest/test_idle_hang.xul
+++ b/dom/plugins/test/mochitest/test_idle_hang.xul
@@ -17,31 +17,34 @@
 <body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
 <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
 </body>
 <script class="testbody" type="application/javascript">
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 SimpleTest.expectChildProcessCrash();
 
-function runTests() {
+async function runTests() {
   // Default plugin hang timeout is too high for mochitests
   var prefs = Cc["@mozilla.org/preferences-service;1"]
                     .getService(Ci.nsIPrefBranch);
   var timeoutPref = "dom.ipc.plugins.timeoutSecs";
   prefs.setIntPref(timeoutPref, 5);
 
   var os = Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService);
   os.addObserver(testObserver, "plugin-crashed", true);
 
   testObserver.idleHang = true;
   document.addEventListener("PluginCrashed", onPluginCrashed, false);
 
   var pluginElement = document.getElementById("plugin1");
+  await SimpleTest.promiseWaitForCondition(() => pluginElement.hang !== undefined,
+                                           "Waited too long for plugin to activate");
+
   try {
     pluginElement.hang(false);
   } catch (e) {
   }
 }
 ]]>
 </script>
 </window>
--- a/dom/plugins/test/mochitest/test_npruntime.xul
+++ b/dom/plugins/test/mochitest/test_npruntime.xul
@@ -12,18 +12,20 @@
   </script>
 <body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
 <embed id="plugin1" type="application/x-test" width="400" height="400"></embed>
 </body>
 <script class="testbody" type="application/javascript">
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 
-function runTests() {
+async function runTests() {
   var pluginElement = document.getElementById("plugin1");
+  await SimpleTest.promiseWaitForCondition(() => pluginElement.identifierToStringTest !== undefined,
+                                           "Waited too long for plugin to activate");
 
   ok(pluginElement.identifierToStringTest('foo') == "foo", "identifierToStringTest failed");
 
   SimpleTest.finish();
 }
 ]]>
 </script>
 </window>
--- a/dom/plugins/test/mochitest/test_privatemode_perwindowpb.xul
+++ b/dom/plugins/test/mochitest/test_privatemode_perwindowpb.xul
@@ -28,20 +28,26 @@ function whenDelayedStartupFinished(aWin
   Services.obs.addObserver(function observer(aSubject, aTopic) {
     if (aWindow == aSubject) {
       Services.obs.removeObserver(observer, aTopic);
       SimpleTest.executeSoon(aCallback);
     }
   }, "browser-delayed-startup-finished");
 }
 
-function runTestsCallback() {
+async function runTestsCallback() {
   var pluginElement1 = document.getElementById("plugin1");
   var pluginElement2 = document.getElementById("plugin2");
 
+  await SimpleTest.promiseWaitForCondition(() => pluginElement1.queryPrivateModeState !== undefined,
+                                           "Waited too long for plugin to activate");
+  await SimpleTest.promiseWaitForCondition(() => pluginElement2.queryPrivateModeState !== undefined,
+                                           "Waited too long for plugin to activate");
+
+
   var state1 = false;
   var state2 = false;
   var exceptionThrown = false;
 
   try {
     state1 = pluginElement1.queryPrivateModeState();
     state2 = pluginElement2.queryPrivateModeState();
   } catch (e) {
--- a/dom/plugins/test/mochitest/test_wmode.xul
+++ b/dom/plugins/test/mochitest/test_wmode.xul
@@ -13,21 +13,25 @@
 <body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
 <embed id="plugin2" type="application/x-test" width="400" height="400" wmode="window"></embed>
 <embed id="plugin1" type="application/x-test" width="400" height="400"></embed>
 </body>
 <script class="testbody" type="application/javascript">
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 
-function runTests() {
+async function runTests() {
   var p1 = document.getElementById("plugin1");
+  await SimpleTest.promiseWaitForCondition(() => p1.hasWidget !== undefined,
+                                           "Waited too long for plugin to activate");
   is(p1.hasWidget(), false, "Plugin should be windowless by default");
 
   var p2 = document.getElementById("plugin2");
+  await SimpleTest.promiseWaitForCondition(() => p2.hasWidget !== undefined,
+                                           "Waited too long for plugin to activate");
   if (navigator.platform.includes("Mac")) {
     is(p2.hasWidget(), false, "Mac does not support windowed plugins");
   } else if (navigator.platform.includes("Win")) {
     is(p2.hasWidget(), true, "Windows supports windowed plugins");
   } else if (navigator.platform.includes("Linux")) {
     is(p2.hasWidget(), false, "Linux does not support windowed plugins");
   }
 
--- a/dom/xul/XULPopupElement.h
+++ b/dom/xul/XULPopupElement.h
@@ -22,17 +22,17 @@ namespace dom {
 class DOMRect;
 class Element;
 class Event;
 class StringOrOpenPopupOptions;
 
 nsXULElement*
 NS_NewXULPopupElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 
-class XULPopupElement final : public nsXULElement
+class XULPopupElement : public nsXULElement
 {
 private:
   nsIFrame* GetFrame(bool aFlushLayout);
 
 public:
   explicit XULPopupElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     : nsXULElement(std::move(aNodeInfo))
   {
new file mode 100644
--- /dev/null
+++ b/dom/xul/XULTooltipElement.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/XULTooltipElement.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsCTooltipTextProvider.h"
+
+namespace mozilla {
+namespace dom {
+
+nsXULElement*
+NS_NewXULTooltipElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+{
+  RefPtr<XULTooltipElement> tooltip = new XULTooltipElement(std::move(aNodeInfo));
+  NS_ENSURE_SUCCESS(tooltip->Init(), nullptr);
+  return tooltip;
+}
+
+nsresult
+XULTooltipElement::Init()
+{
+  // Create the default child label node that will contain the text of the
+  // tooltip.
+  RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+  nodeInfo = mNodeInfo->NodeInfoManager()->GetNodeInfo(nsGkAtoms::label,
+                                                       nullptr,
+                                                       kNameSpaceID_XUL,
+                                                       nsINode::ELEMENT_NODE);
+  nsCOMPtr<Element> label;
+  nsresult rv = NS_NewXULElement(getter_AddRefs(label),
+                                 nodeInfo.forget(), dom::NOT_FROM_PARSER);
+  NS_ENSURE_SUCCESS(rv, rv);
+  label->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+                 NS_LITERAL_STRING("tooltip-label"), false);
+  label->SetAttr(kNameSpaceID_None, nsGkAtoms::flex,
+                 NS_LITERAL_STRING("true"), false);
+  ErrorResult error;
+  AppendChild(*label, error);
+
+  return error.StealNSResult();
+}
+
+nsresult
+XULTooltipElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                nsIPrincipal* aSubjectPrincipal,
+                                bool aNotify)
+{
+  if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::label) {
+    // When the label attribute of this node changes propagate the text down
+    // into child label element.
+    nsCOMPtr<nsIContent> label = GetFirstChild();
+    if (label && label->IsXULElement(nsGkAtoms::label)) {
+      nsAutoString value;
+      if (aValue) {
+        aValue->ToString(value);
+      }
+      nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+        "XULTooltipElement::AfterSetAttr",
+        [label, value]() {
+          Element* labelElement = label->AsElement();
+          labelElement->SetTextContent(value, IgnoreErrors());
+        })
+      );
+    }
+  }
+  return nsXULElement::AfterSetAttr(aNameSpaceID, aName, aValue, aOldValue,
+                                    aSubjectPrincipal, aNotify);
+}
+
+nsresult
+XULTooltipElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+  if (aVisitor.mEvent->mMessage == eXULPopupShowing &&
+      aVisitor.mEvent->IsTrusted() &&
+      !aVisitor.mEvent->DefaultPrevented() &&
+      AttrValueIs(kNameSpaceID_None, nsGkAtoms::page, nsGkAtoms::_true,
+                  eCaseMatters)) {
+    // When the tooltip node has the "page" attribute set to "true" the
+    // tooltip text provider is used to find the tooltip text from page where
+    // mouse is hovering over.
+    nsCOMPtr<nsITooltipTextProvider> textProvider =
+      do_GetService(NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID);
+    nsString text;
+    nsString direction;
+    bool shouldChange = false;
+    textProvider->GetNodeText(GetTriggerNode(), getter_Copies(text),
+                              getter_Copies(direction), &shouldChange);
+    if (shouldChange) {
+      SetAttr(kNameSpaceID_None, nsGkAtoms::label, text, true);
+      SetAttr(kNameSpaceID_None, nsGkAtoms::direction, direction, true);
+    } else {
+      aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+      aVisitor.mEvent->PreventDefault();
+    }
+  }
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/xul/XULTooltipElement.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XULTooltipElement_h__
+#define XULTooltipElement_h__
+
+#include "XULPopupElement.h"
+#include "nsIDOMEventListener.h"
+
+namespace mozilla {
+namespace dom {
+
+nsXULElement*
+NS_NewXULPopupElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+class XULTooltipElement final : public XULPopupElement
+{
+public:
+  explicit XULTooltipElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+    : XULPopupElement(std::move(aNodeInfo))
+  {
+  }
+  nsresult Init();
+
+  virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                nsIPrincipal* aSubjectPrincipal,
+                                bool aNotify) override;
+  virtual nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
+
+protected:
+  virtual ~XULTooltipElement()
+  {
+  }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // XULPopupElement_h
--- a/dom/xul/moz.build
+++ b/dom/xul/moz.build
@@ -21,34 +21,36 @@ if CONFIG['MOZ_XUL']:
         'nsXULSortService.h',
     ]
 
     EXPORTS.mozilla.dom += [
         'XULFrameElement.h',
         'XULMenuElement.h',
         'XULPopupElement.h',
         'XULScrollElement.h',
-        'XULTextElement.h'
+        'XULTextElement.h',
+        'XULTooltipElement.h',
     ]
 
     UNIFIED_SOURCES += [
         'nsXULCommandDispatcher.cpp',
         'nsXULContentSink.cpp',
         'nsXULContentUtils.cpp',
         'nsXULElement.cpp',
         'nsXULPopupListener.cpp',
         'nsXULPrototypeCache.cpp',
         'nsXULPrototypeDocument.cpp',
         'nsXULSortService.cpp',
         'XULDocument.cpp',
         'XULFrameElement.cpp',
         'XULMenuElement.cpp',
         'XULPopupElement.cpp',
         'XULScrollElement.cpp',
-        'XULTextElement.cpp'
+        'XULTextElement.cpp',
+        'XULTooltipElement.cpp',
     ]
 
 XPIDL_SOURCES += [
     'nsIController.idl',
     'nsIControllers.idl',
 ]
 
 XPIDL_MODULE = 'xul'
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -148,21 +148,24 @@ nsXULElement* nsXULElement::Construct(al
   RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
   if (nodeInfo->Equals(nsGkAtoms::label) ||
       nodeInfo->Equals(nsGkAtoms::description)) {
     return new XULTextElement(nodeInfo.forget());
   }
 
   if (nodeInfo->Equals(nsGkAtoms::menupopup) ||
       nodeInfo->Equals(nsGkAtoms::popup) ||
-      nodeInfo->Equals(nsGkAtoms::panel) ||
-      nodeInfo->Equals(nsGkAtoms::tooltip)) {
+      nodeInfo->Equals(nsGkAtoms::panel)) {
     return NS_NewXULPopupElement(nodeInfo.forget());
   }
 
+  if (nodeInfo->Equals(nsGkAtoms::tooltip)) {
+    return NS_NewXULTooltipElement(nodeInfo.forget());
+  }
+
   if (nodeInfo->Equals(nsGkAtoms::iframe) ||
       nodeInfo->Equals(nsGkAtoms::browser) ||
       nodeInfo->Equals(nsGkAtoms::editor)) {
     return new XULFrameElement(nodeInfo.forget());
   }
 
   if (nodeInfo->Equals(nsGkAtoms::menu) ||
       nodeInfo->Equals(nsGkAtoms::menulist)) {
--- a/ipc/glue/ScopedXREEmbed.cpp
+++ b/ipc/glue/ScopedXREEmbed.cpp
@@ -62,31 +62,31 @@ ScopedXREEmbed::Start()
 #ifdef OS_MACOSX
   if (XRE_IsContentProcess()) {
     // We're an XPCOM-using subprocess.  Walk out of
     // [subprocess].app/Contents/MacOS to the real GRE dir.
     rv = localFile->GetParent(getter_AddRefs(parent));
     if (NS_FAILED(rv))
       return;
 
-    localFile = do_QueryInterface(parent);
+    localFile = parent;
     NS_ENSURE_TRUE_VOID(localFile);
 
     rv = localFile->GetParent(getter_AddRefs(parent));
     if (NS_FAILED(rv))
       return;
 
-    localFile = do_QueryInterface(parent);
+    localFile = parent;
     NS_ENSURE_TRUE_VOID(localFile);
 
     rv = localFile->GetParent(getter_AddRefs(parent));
     if (NS_FAILED(rv))
       return;
 
-    localFile = do_QueryInterface(parent);
+    localFile = parent;
     NS_ENSURE_TRUE_VOID(localFile);
 
     rv = localFile->SetNativeLeafName(NS_LITERAL_CSTRING("Resources"));
     if (NS_FAILED(rv)) {
       return;
     }
   }
 #endif
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -589,17 +589,18 @@ def _cxxConstRefType(ipdltype, side):
     if ipdltype.isIPDL() and ipdltype.isShmem():
         t.ref = 1
         return t
     if ipdltype.isIPDL() and ipdltype.isByteBuf():
         t.ref = 1
         return t
     if ipdltype.isIPDL() and ipdltype.isArray():
         # Keep same constness as inner type.
-        t.const = _cxxConstRefType(ipdltype.basetype, side).const
+        inner = _cxxConstRefType(ipdltype.basetype, side)
+        t.const = inner.const or not inner.ref
         t.ref = 1
         return t
     if ipdltype.isCxx() and ipdltype.isRefcounted():
         # Use T* instead of const RefPtr<T>&
         t = t.T
         t.ptr = 1
         return t
     t.const = 1
--- a/ipc/ipdl/test/cxx/IPDLUnitTests.template.cpp
+++ b/ipc/ipdl/test/cxx/IPDLUnitTests.template.cpp
@@ -254,25 +254,26 @@ QuitXPCOM()
   appShell->Exit();
 }
 
 void
 DeleteSubprocess(MessageLoop* uiLoop)
 {
   // pong to QuitXPCOM
   delete gSubprocess;
-  uiLoop->PostTask(NewRunnableFunction(QuitXPCOM));
+  uiLoop->PostTask(NewRunnableFunction("QuitXPCOM", QuitXPCOM));
 }
 
 void
 DeferredParentShutdown()
 {
     // ping to DeleteSubprocess
     XRE_GetIOMessageLoop()->PostTask(
-        NewRunnableFunction(DeleteSubprocess, MessageLoop::current()));
+        NewRunnableFunction("DeleteSubprocess", DeleteSubprocess,
+                            MessageLoop::current()));
 }
 
 void
 TryThreadedShutdown()
 {
     // Stop if either:
     // - the child has not finished,
     // - the parent has not finished,
@@ -296,41 +297,41 @@ ChildCompleted()
 }
 
 void
 QuitParent()
 {
     if (gChildThread) {
         gParentDone = true;
         MessageLoop::current()->PostTask(
-            NewRunnableFunction(TryThreadedShutdown));
+            NewRunnableFunction("TryThreadedShutdown", TryThreadedShutdown));
     } else {
         // defer "real" shutdown to avoid *Channel::Close() racing with the
         // deletion of the subprocess
         MessageLoop::current()->PostTask(
-            NewRunnableFunction(DeferredParentShutdown));
+            NewRunnableFunction("DeferredParentShutdown", DeferredParentShutdown));
     }
 }
 
 static void
 ChildDie()
 {
     DeleteChildActor();
     XRE_ShutdownChildProcess();
 }
 
 void
 QuitChild()
 {
     if (gChildThread) { // Threaded-mode test
         gParentMessageLoop->PostTask(
-            NewRunnableFunction(ChildCompleted));
+            NewRunnableFunction("ChildCompleted", ChildCompleted));
     } else { // Process-mode test
         MessageLoop::current()->PostTask(
-            NewRunnableFunction(ChildDie));
+            NewRunnableFunction("ChildDie", ChildDie));
     }
 }
 
 } // namespace _ipdltest
 } // namespace mozilla
 
 
 //-----------------------------------------------------------------------------
--- a/ipc/ipdl/test/cxx/TestCrashCleanup.cpp
+++ b/ipc/ipdl/test/cxx/TestCrashCleanup.cpp
@@ -40,17 +40,17 @@ void DeleteTheWorld()
     // needs to be synchronous to avoid affecting event ordering on
     // the main thread
     Mutex mutex("TestCrashCleanup.DeleteTheWorld.mutex");
     CondVar cvar(mutex, "TestCrashCleanup.DeleteTheWorld.cvar");
 
     MutexAutoLock lock(mutex);
 
     XRE_GetIOMessageLoop()->PostTask(
-      NewRunnableFunction(DeleteSubprocess, &mutex, &cvar));
+      NewRunnableFunction("DeleteSubprocess", DeleteSubprocess, &mutex, &cvar));
 
     cvar.Wait();
 }
 
 void Done()
 {
   static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
   nsCOMPtr<nsIAppShell> appShell (do_GetService(kAppShellCID));
@@ -74,24 +74,24 @@ TestCrashCleanupParent::~TestCrashCleanu
         fail("should have been ActorDestroy()d!");
 }
 
 void
 TestCrashCleanupParent::Main()
 {
     // NB: has to be enqueued before IO thread's error notification
     MessageLoop::current()->PostTask(
-        NewRunnableFunction(DeleteTheWorld));
+        NewRunnableFunction("DeleteTheWorld", DeleteTheWorld));
 
     if (CallDIEDIEDIE())
         fail("expected an error!");
 
     Close();
 
-    MessageLoop::current()->PostTask(NewRunnableFunction(Done));
+    MessageLoop::current()->PostTask(NewRunnableFunction("Done", Done));
 }
 
 
 //-----------------------------------------------------------------------------
 // child
 
 TestCrashCleanupChild::TestCrashCleanupChild()
 {
--- a/ipc/ipdl/test/cxx/TestEndpointOpens.cpp
+++ b/ipc/ipdl/test/cxx/TestEndpointOpens.cpp
@@ -67,17 +67,17 @@ TestEndpointOpensParent::RecvStartSubpro
 
   gParentThread = new Thread("ParentThread");
   if (!gParentThread->Start()) {
     fail("starting parent thread");
   }
 
   TestEndpointOpensOpenedParent* a = new TestEndpointOpensOpenedParent();
   gParentThread->message_loop()->PostTask(
-    NewRunnableFunction(OpenParent, a, std::move(endpoint)));
+    NewRunnableFunction("OpenParent", OpenParent, a, std::move(endpoint)));
 
   return IPC_OK();
 }
 
 void
 TestEndpointOpensParent::ActorDestroy(ActorDestroyReason why)
 {
   // Stops the thread and joins it
@@ -132,17 +132,18 @@ TestEndpointOpensOpenedParent::ActorDest
   if (NormalShutdown != why) {
     fail("unexpected destruction B!");
   }
 
   // ActorDestroy() is just a callback from IPDL-generated code,
   // which needs the top-level actor (this) to stay alive a little
   // longer so other things can be cleaned up.
   gParentThread->message_loop()->PostTask(
-    NewRunnableFunction(ShutdownTestEndpointOpensOpenedParent,
+    NewRunnableFunction("ShutdownTestEndpointOpensOpenedParent",
+                        ShutdownTestEndpointOpensOpenedParent,
                         this, GetTransport()));
 }
 
 //-----------------------------------------------------------------------------
 // child
 
 static TestEndpointOpensChild* gOpensChild;
 // Thread on which TestEndpointOpensOpenedChild runs
@@ -188,17 +189,17 @@ TestEndpointOpensChild::RecvStart()
 
   gChildThread = new Thread("ChildThread");
   if (!gChildThread->Start()) {
     fail("starting child thread");
   }
 
   TestEndpointOpensOpenedChild* a = new TestEndpointOpensOpenedChild();
   gChildThread->message_loop()->PostTask(
-    NewRunnableFunction(OpenChild, a, std::move(child)));
+    NewRunnableFunction("OpenChild", OpenChild, a, std::move(child)));
 
   if (!SendStartSubprotocol(parent)) {
     fail("send StartSubprotocol");
   }
 
   return IPC_OK();
 }
 
@@ -269,13 +270,14 @@ TestEndpointOpensOpenedChild::ActorDestr
     fail("unexpected destruction D!");
   }
 
   // ActorDestroy() is just a callback from IPDL-generated code,
   // which needs the top-level actor (this) to stay alive a little
   // longer so other things can be cleaned up.  Defer shutdown to
   // let cleanup finish.
   gChildThread->message_loop()->PostTask(
-    NewRunnableFunction(ShutdownTestEndpointOpensOpenedChild,
+    NewRunnableFunction("ShutdownTestEndpointOpensOpenedChild",
+                        ShutdownTestEndpointOpensOpenedChild,
                         this, GetTransport()));
 }
 
 } // namespace mozilla
--- a/ipc/ipdl/test/cxx/TestInterruptErrorCleanup.cpp
+++ b/ipc/ipdl/test/cxx/TestInterruptErrorCleanup.cpp
@@ -40,17 +40,17 @@ void DeleteTheWorld()
     // needs to be synchronous to avoid affecting event ordering on
     // the main thread
     Mutex mutex("TestInterruptErrorCleanup.DeleteTheWorld.mutex");
     CondVar cvar(mutex, "TestInterruptErrorCleanup.DeleteTheWorld.cvar");
 
     MutexAutoLock lock(mutex);
 
     XRE_GetIOMessageLoop()->PostTask(
-      NewRunnableFunction(DeleteSubprocess, &mutex, &cvar));
+      NewRunnableFunction("DeleteSubprocess", DeleteSubprocess, &mutex, &cvar));
 
     cvar.Wait();
 }
 
 void Done()
 {
   static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
   nsCOMPtr<nsIAppShell> appShell (do_GetService(kAppShellCID));
@@ -96,34 +96,34 @@ TestInterruptErrorCleanupParent::Main()
     //  - use-after-free of parentActor
     //  - use-after-free of channel
     //
     // Because of legacy constraints related to nsNPAPI* code, we need
     // to ensure that this sequence of events can occur without
     // errors/crashes.
 
     MessageLoop::current()->PostTask(
-        NewRunnableFunction(DeleteTheWorld));
+        NewRunnableFunction("DeleteTheWorld", DeleteTheWorld));
 
     // it's a failure if this *succeeds*
     if (CallError())
         fail("expected an error!");
 
     if (!mGotProcessingError)
         fail("expected a ProcessingError() notification");
 
     // it's OK to Close() a channel after an error, because nsNPAPI*
     // wants to do this
     Close();
 
     // we know that this event *must* be after the MaybeError
     // notification enqueued by AsyncChannel, because that event is
     // enqueued within the same mutex that ends up signaling the
     // wakeup-on-error of |CallError()| above
-    MessageLoop::current()->PostTask(NewRunnableFunction(Done));
+    MessageLoop::current()->PostTask(NewRunnableFunction("Done", Done));
 }
 
 void
 TestInterruptErrorCleanupParent::ProcessingError(Result aCode, const char* aReason)
 {
     if (aCode != MsgDropped)
         fail("unexpected processing error");
     mGotProcessingError = true;
--- a/ipc/ipdl/test/cxx/TestInterruptShutdownRace.cpp
+++ b/ipc/ipdl/test/cxx/TestInterruptShutdownRace.cpp
@@ -70,20 +70,21 @@ TestInterruptShutdownRaceParent::StartSh
     if (CallExit())
         fail("connection was supposed to be interrupted");
 
     Close();
 
     delete static_cast<TestInterruptShutdownRaceParent*>(gParentActor);
     gParentActor = nullptr;
 
-    XRE_GetIOMessageLoop()->PostTask(NewRunnableFunction(DeleteSubprocess));
+    XRE_GetIOMessageLoop()->PostTask(NewRunnableFunction("DeleteSubprocess",
+                                                         DeleteSubprocess));
 
     // this is ordered after the OnMaybeDequeueOne event in the queue
-    MessageLoop::current()->PostTask(NewRunnableFunction(Done));
+    MessageLoop::current()->PostTask(NewRunnableFunction("Done", Done));
 
     // |this| has been deleted, be mindful
 }
 
 mozilla::ipc::IPCResult
 TestInterruptShutdownRaceParent::RecvOrphan()
 {
     // it would be nice to fail() here, but we'll process this message
--- a/ipc/ipdl/test/cxx/TestStackHooks.cpp
+++ b/ipc/ipdl/test/cxx/TestStackHooks.cpp
@@ -78,17 +78,18 @@ TestStackHooksChild::RecvStart()
     if (!mOnStack)
         fail("missed stack notification");
 
     if (0 != mIncallDepth)
         fail("EnteredCall/ExitedCall malfunction");
 
     // kick off tests from a runnable so that we can start with
     // MessageChannel code on the C++ stack
-    MessageLoop::current()->PostTask(NewRunnableFunction(RunTestsFn));
+    MessageLoop::current()->PostTask(NewRunnableFunction("RunTestsFn",
+                                                         RunTestsFn));
 
     return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TestStackHooksChild::AnswerStackFrame()
 {
     ++mNumAnswerStackFrame;
--- a/ipc/ipdl/test/cxx/TestSyncHang.cpp
+++ b/ipc/ipdl/test/cxx/TestSyncHang.cpp
@@ -31,30 +31,32 @@ DeleteSyncHangSubprocess(MessageLoop* ui
   delete gSyncHangSubprocess;
 }
 
 void
 DeferredSyncHangParentShutdown()
 {
   // ping to DeleteSubprocess
   XRE_GetIOMessageLoop()->PostTask(
-      NewRunnableFunction(DeleteSyncHangSubprocess, MessageLoop::current()));
+      NewRunnableFunction("DeleteSyncHangSubprocess", DeleteSyncHangSubprocess,
+                          MessageLoop::current()));
 }
 
 void
 TestSyncHangParent::Main()
 {
   vector<string> args;
   args.push_back("fake/path");
   gSyncHangSubprocess = new mozilla::ipc::GeckoChildProcessHost(GeckoProcessType_Plugin);
   bool launched = gSyncHangSubprocess->SyncLaunch(args, 2);
   if (launched)
     fail("Calling SyncLaunch with an invalid path should return false");
 
-  MessageLoop::current()->PostTask(NewRunnableFunction(DeferredSyncHangParentShutdown));
+  MessageLoop::current()->PostTask(NewRunnableFunction("DeferredSyncHangParentShutdown",
+                                                       DeferredSyncHangParentShutdown));
   Close();
 }
 
 //-----------------------------------------------------------------------------
 // child
 
 TestSyncHangChild::TestSyncHangChild()
 {
--- a/js/xpconnect/idl/nsIXPConnect.idl
+++ b/js/xpconnect/idl/nsIXPConnect.idl
@@ -59,36 +59,16 @@ interface nsIXPConnectWrappedNative : ns
     nsISupports* Native() const { return mIdentity; }
 
 protected:
     nsCOMPtr<nsISupports> mIdentity;
 public:
 %}
 };
 
-%{C++
-
-inline
-const nsQueryInterface
-do_QueryWrappedNative(nsIXPConnectWrappedNative *aWrappedNative)
-{
-    return nsQueryInterface(aWrappedNative->Native());
-}
-
-inline
-const nsQueryInterfaceWithError
-do_QueryWrappedNative(nsIXPConnectWrappedNative *aWrappedNative,
-                      nsresult *aError)
-
-{
-    return nsQueryInterfaceWithError(aWrappedNative->Native(), aError);
-}
-
-%}
-
 [builtinclass, uuid(3a01b0d6-074b-49ed-bac3-08c76366cae4)]
 interface nsIXPConnectWrappedJS : nsIXPConnectJSObjectHolder
 {
     /* attribute 'JSObject' inherited from nsIXPConnectJSObjectHolder */
     readonly attribute InterfaceInfoPtr InterfaceInfo;
     readonly attribute nsIIDPtr         InterfaceIID;
 
     // Match the GetJSObject() signature.
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -1965,17 +1965,17 @@ nsXPCComponents_Constructor::CallOrConst
         RootedValue val(cx);
         if (!JS_GetPropertyById(cx, ifacesObj, id, &val) || val.isPrimitive()) {
             return ThrowAndFail(NS_ERROR_XPC_BAD_IID, cx, _retval);
         }
 
         nsCOMPtr<nsIXPConnectWrappedNative> wn;
         if (NS_FAILED(xpc->GetWrappedNativeOfJSObject(cx, &val.toObject(),
                                                       getter_AddRefs(wn))) || !wn ||
-            !(cInterfaceID = do_QueryWrappedNative(wn))) {
+            !(cInterfaceID = do_QueryInterface(wn->Native()))) {
             return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval);
         }
     } else {
         const nsXPTInterfaceInfo* info =
             nsXPTInterfaceInfo::ByIID(NS_GET_IID(nsISupports));
 
         if (info) {
             cInterfaceID = nsJSIID::NewID(info);
@@ -2013,17 +2013,17 @@ nsXPCComponents_Constructor::CallOrConst
         RootedValue val(cx);
         if (!JS_GetPropertyById(cx, classesObj, id, &val) || val.isPrimitive()) {
             return ThrowAndFail(NS_ERROR_XPC_BAD_CID, cx, _retval);
         }
 
         nsCOMPtr<nsIXPConnectWrappedNative> wn;
         if (NS_FAILED(xpc->GetWrappedNativeOfJSObject(cx, val.toObjectOrNull(),
                                                       getter_AddRefs(wn))) || !wn ||
-            !(cClassID = do_QueryWrappedNative(wn))) {
+            !(cClassID = do_QueryInterface(wn->Native()))) {
             return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval);
         }
     }
 
     nsCOMPtr<nsIXPCConstructor> ctor = new nsXPCConstructor(cClassID, cInterfaceID, cInitializer);
     RootedObject newObj(cx);
 
     if (NS_FAILED(xpc->WrapNative(cx, obj, ctor, NS_GET_IID(nsIXPCConstructor), newObj.address())) || !newObj) {
--- a/js/xpconnect/src/XPCRuntimeService.cpp
+++ b/js/xpconnect/src/XPCRuntimeService.cpp
@@ -124,17 +124,17 @@ NS_IMETHODIMP
 BackstagePass::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc)
 {
     return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
 BackstagePass::Finalize(nsIXPConnectWrappedNative* wrapper, JSFreeOp * fop, JSObject * obj)
 {
-    nsCOMPtr<nsIGlobalObject> bsp(do_QueryWrappedNative(wrapper));
+    nsCOMPtr<nsIGlobalObject> bsp(do_QueryInterface(wrapper->Native()));
     MOZ_ASSERT(bsp);
     static_cast<BackstagePass*>(bsp.get())->ForgetGlobalObject();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 BackstagePass::PreCreate(nsISupports* nativeObj, JSContext* cx,
                          JSObject* globalObj, JSObject** parentObj)
--- a/js/xpconnect/tests/chrome/test_weakmap_keys_preserved2.xul
+++ b/js/xpconnect/tests/chrome/test_weakmap_keys_preserved2.xul
@@ -13,17 +13,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <a id="testelem" href="https://bugzilla.mozilla.org/show_bug.cgi?id="
      target="_blank">Mozilla Bug 673468</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
   /** Test for Bug 673468 **/
-
+  SimpleTest.waitForExplicitFinish();
   SpecialPowers.DOMWindowUtils.garbageCollect();
 
   let get_live_dom = function () {
     return document.getElementById("testelem");
   };
 
   let wrappers_as_keys_test = function () {
     let e = new MessageEvent("foo", { bubbles: false, cancellable: false,
@@ -68,13 +68,14 @@ https://bugzilla.mozilla.org/show_bug.cg
     is(value.children.length, 1, "children have wrong length");
   }
 
   Cu.schedulePreciseGC(function () {
     SpecialPowers.DOMWindowUtils.cycleCollect();
     SpecialPowers.DOMWindowUtils.garbageCollect();
 
     check_wrappers_as_keys();
+    SimpleTest.finish();
   });
 
   ]]>
   </script>
 </window>
--- a/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
@@ -1216,17 +1216,17 @@ public class BrowserSearch extends HomeF
                 return title;
             }
 
             final SpannableStringBuilder sb = new SpannableStringBuilder(title);
 
             // Find matching substrings in title field in TwoLinePageRow, ignoring cases. This needs
             // to be done indirectly through a case-insensitive matcher because the lower-case
             // version of a string might have a different number of characters than the original.
-            Pattern pattern = Pattern.compile(mSearchTerm, Pattern.CASE_INSENSITIVE);
+            Pattern pattern = Pattern.compile(mSearchTerm, Pattern.CASE_INSENSITIVE | Pattern.LITERAL);
             Matcher matcher = pattern.matcher(title.toString());
 
             while (matcher.find()) {
                 final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
                 sb.setSpan(boldSpan, matcher.start(), matcher.end(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
             }
 
             return sb;
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -229,17 +229,17 @@ GetJSValueAsURI(JSContext* aCtx,
                 const JS::Value& aValue) {
   if (!aValue.isPrimitive()) {
     nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
 
     nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
     nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, aValue.toObjectOrNull(),
                                                   getter_AddRefs(wrappedObj));
     NS_ENSURE_SUCCESS(rv, nullptr);
-    nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj);
+    nsCOMPtr<nsIURI> uri = do_QueryInterface(wrappedObj->Native());
     return uri.forget();
   }
   return nullptr;
 }
 
 /**
  * Obtains an nsIURI from the "uri" property of a JSObject.
  *
--- a/toolkit/content/widgets/datetimepopup.xml
+++ b/toolkit/content/widgets/datetimepopup.xml
@@ -6,19 +6,16 @@
 
 <bindings id="dateTimePopupBindings"
    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="datetime-popup"
            extends="chrome://global/content/bindings/popup.xml#arrowpanel">
-    <resources>
-      <stylesheet src="chrome://global/skin/datetimepopup.css"/>
-    </resources>
     <implementation>
       <property name="dateTimePopupFrame">
         <getter>
           let frame = this.querySelector("#dateTimePopupFrame");
           if (!frame) {
             frame = this.ownerDocument.createXULElement("iframe");
             frame.id = "dateTimePopupFrame";
             this.appendChild(frame);
--- a/toolkit/content/widgets/popup.xml
+++ b/toolkit/content/widgets/popup.xml
@@ -258,128 +258,16 @@
         }
       </handler>
       <handler event="popuppositioned" phase="target">
         this.adjustArrowPosition();
       </handler>
     </handlers>
   </binding>
 
-  <binding id="tooltip">
-    <content>
-      <children>
-        <xul:label class="tooltip-label" xbl:inherits="xbl:text=label" flex="1"/>
-      </children>
-    </content>
-
-    <implementation>
-      <field name="_mouseOutCount">0</field>
-      <field name="_isMouseOver">false</field>
-
-      <property name="label"
-                onget="return this.getAttribute('label');"
-                onset="this.setAttribute('label', val); return val;"/>
-
-      <property name="page" onset="if (val) this.setAttribute('page', 'true');
-                                   else this.removeAttribute('page');
-                                   return val;"
-                            onget="return this.getAttribute('page') == 'true';"/>
-      <property name="textProvider"
-                readonly="true">
-        <getter>
-        <![CDATA[
-          if (!this._textProvider) {
-            this._textProvider = Cc["@mozilla.org/embedcomp/default-tooltiptextprovider;1"]
-                                 .getService(Ci.nsITooltipTextProvider);
-          }
-          return this._textProvider;
-        ]]>
-        </getter>
-      </property>
-
-      <!-- Given the supplied element within a page, set the tooltip's text to the text
-           for that element. Returns true if text was assigned, and false if the no text
-           is set, which normally would be used to cancel tooltip display.
-        -->
-      <method name="fillInPageTooltip">
-        <parameter name="tipElement"/>
-        <body>
-        <![CDATA[
-          let tttp = this.textProvider;
-          let textObj = {}, dirObj = {};
-          let shouldChangeText = tttp.getNodeText(tipElement, textObj, dirObj);
-          if (shouldChangeText) {
-            this.style.direction = dirObj.value;
-            this.label = textObj.value;
-          }
-          return shouldChangeText;
-        ]]>
-        </body>
-      </method>
-    </implementation>
-
-    <handlers>
-      <handler event="mouseover"><![CDATA[
-        var rel = event.relatedTarget;
-        if (!rel)
-          return;
-
-        // find out if the node we entered from is one of our anonymous children
-        while (rel) {
-          if (rel == this)
-            break;
-          rel = rel.parentNode;
-        }
-
-        // if the exited node is not a descendant of ours, we are entering for the first time
-        if (rel != this)
-          this._isMouseOver = true;
-      ]]></handler>
-
-      <handler event="mouseout"><![CDATA[
-        var rel = event.relatedTarget;
-
-        // relatedTarget is null when the titletip is first shown: a mouseout event fires
-        // because the mouse is exiting the main window and entering the titletip "window".
-        // relatedTarget is also null when the mouse exits the main window completely,
-        // so count how many times relatedTarget was null after titletip is first shown
-        // and hide popup the 2nd time
-        if (!rel) {
-          ++this._mouseOutCount;
-          if (this._mouseOutCount > 1)
-            this.hidePopup();
-          return;
-        }
-
-        // find out if the node we are entering is one of our anonymous children
-        while (rel) {
-          if (rel == this)
-            break;
-          rel = rel.parentNode;
-        }
-
-        // if the entered node is not a descendant of ours, hide the tooltip
-        if (rel != this && this._isMouseOver) {
-          this.hidePopup();
-        }
-      ]]></handler>
-
-      <handler event="popupshowing"><![CDATA[
-        if (this.page && !this.fillInPageTooltip(this.triggerNode)) {
-          event.preventDefault();
-        }
-      ]]></handler>
-
-      <handler event="popuphiding"><![CDATA[
-        this._isMouseOver = false;
-        this._mouseOutCount = 0;
-      ]]></handler>
-    </handlers>
-  </binding>
-
   <binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
     <content>
       <xul:scrollbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
         <children/>
       </xul:scrollbox>
     </content>
     <implementation>
       <field name="AUTOSCROLL_INTERVAL">25</field>
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -366,17 +366,16 @@ menupopup,
 panel,
 tooltip {
   display: -moz-popup;
   z-index: 2147483647;
   text-shadow: none;
 }
 
 tooltip {
-  -moz-binding: url("chrome://global/content/bindings/popup.xml#tooltip");
   -moz-box-orient: vertical;
   white-space: pre-wrap;
   margin-top: 21px;
 }
 
 panel[type="arrow"] {
   -moz-binding: url("chrome://global/content/bindings/popup.xml#arrowpanel");
 }
deleted file mode 100644
--- a/toolkit/themes/shared/datetimepopup.css
+++ /dev/null
@@ -1,11 +0,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/. */
-
-@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-
-panel[type="arrow"][side="top"],
-panel[type="arrow"][side="bottom"] {
-  margin-left: 0;
-  margin-right: 0;
-}
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -17,17 +17,16 @@ toolkit.jar:
   skin/classic/global/aboutMemory.css                      (../../shared/aboutMemory.css)
 * skin/classic/global/aboutReader.css                      (../../shared/aboutReader.css)
   skin/classic/global/aboutRights.css                      (../../shared/aboutRights.css)
   skin/classic/global/aboutLicense.css                     (../../shared/aboutLicense.css)
   skin/classic/global/aboutSupport.css                     (../../shared/aboutSupport.css)
   skin/classic/global/appPicker.css                        (../../shared/appPicker.css)
   skin/classic/global/config.css                           (../../shared/config.css)
   skin/classic/global/datetimeinputpickers.css             (../../shared/datetimeinputpickers.css)
-  skin/classic/global/datetimepopup.css                    (../../shared/datetimepopup.css)
   skin/classic/global/numberbox.css                        (../../shared/numberbox.css)
   skin/classic/global/passwordmgr.css                      (../../shared/passwordmgr.css)
   skin/classic/global/icons/autoscroll.svg                 (../../shared/icons/autoscroll.svg)
   skin/classic/global/icons/autoscroll-horizontal.svg      (../../shared/icons/autoscroll-horizontal.svg)
   skin/classic/global/icons/autoscroll-vertical.svg        (../../shared/icons/autoscroll-vertical.svg)
   skin/classic/global/icons/calendar-arrow-left.svg        (../../shared/icons/calendar-arrow-left.svg)
   skin/classic/global/icons/calendar-arrow-right.svg       (../../shared/icons/calendar-arrow-right.svg)
   skin/classic/global/icons/check.svg                      (../../shared/icons/check.svg)
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -1485,17 +1485,17 @@ nsXREDirProvider::GetUserDataDirectoryHo
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsILocalFileMac> dirFileMac = do_QueryInterface(localDir);
   NS_ENSURE_TRUE(dirFileMac, NS_ERROR_UNEXPECTED);
 
   rv = dirFileMac->InitWithFSRef(&fsRef);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  localDir = do_QueryInterface(dirFileMac, &rv);
+  localDir = dirFileMac;
 #elif defined(XP_IOS)
   nsAutoCString userDir;
   if (GetUIKitDirectory(aLocal, userDir)) {
     rv = NS_NewNativeLocalFile(userDir, true, getter_AddRefs(localDir));
   } else {
     rv = NS_ERROR_FAILURE;
   }
   NS_ENSURE_SUCCESS(rv, rv);
--- a/tools/profiler/rust-helper/Cargo.toml
+++ b/tools/profiler/rust-helper/Cargo.toml
@@ -1,15 +1,20 @@
 [package]
 name = "profiler_helper"
 version = "0.1.0"
 authors = ["Markus Stange <mstange@themasta.com>"]
 
 [dependencies]
 memmap = "0.6.2"
-object = { version = "0.10.0", optional = true, default-features = false }
+
+[dependencies.object]
+version = "0.10.0"
+optional = true
+default-features = false
+features = ["std"]
 
 [dependencies.thin-vec]
 version = "0.1.0"
 features = ["gecko-ffi"]
 
 [features]
 parse_elf = ["object"]
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -334,17 +334,17 @@ static nsresult GetDownloadDirectory(nsI
 
   nsresult rv;
   if (downloadDir) {
     nsCOMPtr<nsIFile> ldir;
     rv = NS_NewNativeLocalFile(downloadDir->ToCString(),
                                true, getter_AddRefs(ldir));
 
     NS_ENSURE_SUCCESS(rv, rv);
-    dir = do_QueryInterface(ldir);
+    dir = ldir;
 
     // If we're not checking for availability we're done.
     if (aSkipChecks) {
       dir.forget(_directory);
       return NS_OK;
     }
   }
   else {
--- a/widget/cocoa/nsSound.mm
+++ b/widget/cocoa/nsSound.mm
@@ -55,17 +55,17 @@ nsSound::OnStreamComplete(nsIStreamLoade
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 NS_IMETHODIMP
 nsSound::Play(nsIURL *aURL)
 {
-  nsCOMPtr<nsIURI> uri(do_QueryInterface(aURL));
+  nsCOMPtr<nsIURI> uri(aURL);
   nsCOMPtr<nsIStreamLoader> loader;
   return NS_NewStreamLoader(getter_AddRefs(loader),
                             uri,
                             this, // aObserver
                             nsContentUtils::GetSystemPrincipal(),
                             nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                             nsIContentPolicy::TYPE_OTHER);
 }
--- a/widget/windows/nsDragService.cpp
+++ b/widget/windows/nsDragService.cpp
@@ -166,17 +166,17 @@ nsDragService::CreateDragImage(nsINode *
 nsresult
 nsDragService::InvokeDragSessionImpl(nsIArray* anArrayTransferables,
                                      const Maybe<CSSIntRegion>& aRegion,
                                      uint32_t aActionType)
 {
   // Try and get source URI of the items that are being dragged
   nsIURI *uri = nullptr;
 
-  nsCOMPtr<nsIDocument> doc(do_QueryInterface(mSourceDocument));
+  nsCOMPtr<nsIDocument> doc(mSourceDocument);
   if (doc) {
     uri = doc->GetDocumentURI();
   }
 
   uint32_t numItemsToDrag = 0;
   nsresult rv = anArrayTransferables->GetLength(&numItemsToDrag);
   if (!numItemsToDrag)
     return NS_ERROR_FAILURE;
--- a/widget/windows/nsFilePicker.cpp
+++ b/widget/windows/nsFilePicker.cpp
@@ -787,17 +787,17 @@ nsFilePicker::RememberLastUsedDirectory(
   if (!file || NS_FAILED(file->InitWithPath(mUnicodeFile))) {
     NS_WARNING("RememberLastUsedDirectory failed to init file path.");
     return;
   }
 
   nsCOMPtr<nsIFile> dir;
   nsAutoString newDir;
   if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) ||
-      !(mDisplayDirectory = do_QueryInterface(dir)) ||
+      !(mDisplayDirectory = dir) ||
       NS_FAILED(mDisplayDirectory->GetPath(newDir)) ||
       newDir.IsEmpty()) {
     NS_WARNING("RememberLastUsedDirectory failed to get parent directory.");
     return;
   }
 
   if (mLastUsedUnicodeDirectory) {
     free(mLastUsedUnicodeDirectory);
--- a/widget/windows/nsPrintDialogWin.cpp
+++ b/widget/windows/nsPrintDialogWin.cpp
@@ -173,18 +173,17 @@ nsPrintDialogServiceWin::GetHWNDForDOMWi
       site->GetSiteWindow(reinterpret_cast<void**>(&w));
       return w;
     }
   }
 
   // Now we might be the Browser so check this path
   nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
 
-  nsCOMPtr<nsIDocShellTreeItem> treeItem =
-    do_QueryInterface(window->GetDocShell());
+  nsCOMPtr<nsIDocShellTreeItem> treeItem = window->GetDocShell();
   if (!treeItem)
     return nullptr;
 
   nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
   treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
   if (!treeOwner)
     return nullptr;