merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sun, 11 Jun 2017 10:34:24 +0200
changeset 411487 a040e8315026ea31bd92aa26333d69f689b1ccd7
parent 411447 8a990794c2ee923ccd839750912d10ff3a690a2a (current diff)
parent 411486 76831b11ec441613689914a1eeb5a073f3134709 (diff)
child 411514 799d43edb324395cf02da6b028e803712334615f
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central. r=merge a=merge MozReview-Commit-ID: Jhnzx44tZLY
browser/components/extensions/ext-c-contextMenus.js
browser/components/extensions/ext-contextMenus.js
browser/components/extensions/schemas/context_menus.json
browser/components/extensions/schemas/context_menus_internal.json
browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1566,16 +1566,21 @@ var BookmarkingUI = {
   },
 
   _uninitView: function BUI__uninitView() {
     // When an element with a placesView attached is removed and re-inserted,
     // XBL reapplies the binding causing any kind of issues and possible leaks,
     // so kill current view and let popupshowing generate a new one.
     if (this.button._placesView)
       this.button._placesView.uninit();
+    // Also uninit the main menubar placesView, since it would have the same
+    // issues.
+    let menubar = document.getElementById("bookmarksMenu");
+    if (menubar && menubar._placesView)
+      menubar._placesView.uninit();
 
     // We have to do the same thing for the "special" views underneath the
     // the bookmarks menu.
     const kSpecialViewNodeIDs = ["BMB_bookmarksToolbar", "BMB_unsortedBookmarks"];
     for (let viewNodeID of kSpecialViewNodeIDs) {
       let elem = document.getElementById(viewNodeID);
       if (elem && elem._placesView) {
         elem._placesView.uninit();
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1176,89 +1176,86 @@ function RedirectLoad({ target: browser,
         LoadInOtherProcess(browser, data.loadOptions, data.historyIndex);
       }
     };
     Services.obs.addObserver(delayedStartupFinished,
                              "browser-delayed-startup-finished");
   }
 }
 
-addEventListener("DOMContentLoaded", function onDCL() {
-  removeEventListener("DOMContentLoaded", onDCL);
-
-  // There are some windows, like macBrowserOverlay.xul, that
-  // load browser.js, but never load tabbrowser.xml. We can ignore
-  // those cases.
-  if (!gBrowser || !gBrowser.updateBrowserRemoteness) {
-    return;
-  }
-
-  window.QueryInterface(Ci.nsIInterfaceRequestor)
-        .getInterface(nsIWebNavigation)
-        .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
-        .QueryInterface(Ci.nsIInterfaceRequestor)
-        .getInterface(Ci.nsIXULWindow)
-        .XULBrowserWindow = window.XULBrowserWindow;
-  window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
-    new nsBrowserAccess();
-
-  let initBrowser =
-    document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
-
-  // remoteType and sameProcessAsFrameLoader are passed through to
-  // updateBrowserRemoteness as part of an options object, which itself defaults
-  // to an empty object. So defaulting them to undefined here will cause the
-  // default behavior in updateBrowserRemoteness if they don't get set.
-  let isRemote = gMultiProcessBrowser;
-  let remoteType;
-  let sameProcessAsFrameLoader;
-  if (window.arguments) {
-    let argToLoad = window.arguments[0];
-    if (argToLoad instanceof XULElement) {
-      // The window's first argument is a tab if and only if we are swapping tabs.
-      // We must set the browser's usercontextid before updateBrowserRemoteness(),
-      // so that the newly created remote tab child has the correct usercontextid.
-      if (argToLoad.hasAttribute("usercontextid")) {
-        initBrowser.setAttribute("usercontextid",
-                                 argToLoad.getAttribute("usercontextid"));
-      }
-
-      let linkedBrowser = argToLoad.linkedBrowser;
-      if (linkedBrowser) {
-        remoteType = linkedBrowser.remoteType;
-        isRemote = remoteType != E10SUtils.NOT_REMOTE;
-        sameProcessAsFrameLoader = linkedBrowser.frameLoader;
-      }
-    } else if (argToLoad instanceof String) {
-      // argToLoad is String, so should be a URL.
-      remoteType = E10SUtils.getRemoteTypeForURI(argToLoad, gMultiProcessBrowser);
-      isRemote = remoteType != E10SUtils.NOT_REMOTE;
-    } else if (argToLoad instanceof Ci.nsIArray) {
-      // argToLoad is nsIArray, so should be an array of URLs, set the remote
-      // type for the initial browser to match the first one.
-      let urisstring = argToLoad.queryElementAt(0, Ci.nsISupportsString);
-      remoteType = E10SUtils.getRemoteTypeForURI(urisstring.data,
-                                                 gMultiProcessBrowser);
-      isRemote = remoteType != E10SUtils.NOT_REMOTE;
-    }
-  }
-
-  gBrowser.updateBrowserRemoteness(initBrowser, isRemote, {
-    remoteType, sameProcessAsFrameLoader
-  });
-});
+if (document.documentElement.getAttribute("windowtype") == "navigator:browser") {
+  addEventListener("DOMContentLoaded", function() {
+    gBrowserInit.onDOMContentLoaded();
+  }, { once: true });
+}
 
 let _resolveDelayedStartup;
 var delayedStartupPromise = new Promise(resolve => {
   _resolveDelayedStartup = resolve;
 });
 
 var gBrowserInit = {
   delayedStartupFinished: false,
 
+  onDOMContentLoaded() {
+    window.QueryInterface(Ci.nsIInterfaceRequestor)
+          .getInterface(nsIWebNavigation)
+          .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+          .QueryInterface(Ci.nsIInterfaceRequestor)
+          .getInterface(Ci.nsIXULWindow)
+          .XULBrowserWindow = window.XULBrowserWindow;
+    window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
+      new nsBrowserAccess();
+
+    let initBrowser =
+      document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
+
+    // remoteType and sameProcessAsFrameLoader are passed through to
+    // updateBrowserRemoteness as part of an options object, which itself defaults
+    // to an empty object. So defaulting them to undefined here will cause the
+    // default behavior in updateBrowserRemoteness if they don't get set.
+    let isRemote = gMultiProcessBrowser;
+    let remoteType;
+    let sameProcessAsFrameLoader;
+    if (window.arguments) {
+      let argToLoad = window.arguments[0];
+      if (argToLoad instanceof XULElement) {
+        // The window's first argument is a tab if and only if we are swapping tabs.
+        // We must set the browser's usercontextid before updateBrowserRemoteness(),
+        // so that the newly created remote tab child has the correct usercontextid.
+        if (argToLoad.hasAttribute("usercontextid")) {
+          initBrowser.setAttribute("usercontextid",
+                                   argToLoad.getAttribute("usercontextid"));
+        }
+
+        let linkedBrowser = argToLoad.linkedBrowser;
+        if (linkedBrowser) {
+          remoteType = linkedBrowser.remoteType;
+          isRemote = remoteType != E10SUtils.NOT_REMOTE;
+          sameProcessAsFrameLoader = linkedBrowser.frameLoader;
+        }
+      } else if (argToLoad instanceof String) {
+        // argToLoad is String, so should be a URL.
+        remoteType = E10SUtils.getRemoteTypeForURI(argToLoad, gMultiProcessBrowser);
+        isRemote = remoteType != E10SUtils.NOT_REMOTE;
+      } else if (argToLoad instanceof Ci.nsIArray) {
+        // argToLoad is nsIArray, so should be an array of URLs, set the remote
+        // type for the initial browser to match the first one.
+        let urisstring = argToLoad.queryElementAt(0, Ci.nsISupportsString);
+        remoteType = E10SUtils.getRemoteTypeForURI(urisstring.data,
+                                                   gMultiProcessBrowser);
+        isRemote = remoteType != E10SUtils.NOT_REMOTE;
+      }
+    }
+
+    gBrowser.updateBrowserRemoteness(initBrowser, isRemote, {
+      remoteType, sameProcessAsFrameLoader
+    });
+  },
+
   onLoad() {
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver);
 
     Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
 
     window.addEventListener("AppCommand", HandleAppCommandEvent, true);
 
     // These routines add message listeners. They must run before
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -76,13 +76,13 @@
       <popupnotificationcontent orient="vertical">
         <description id="addon-webext-perm-text" class="addon-webext-perm-text"/>
         <label id="addon-webext-perm-intro" class="addon-webext-perm-text"/>
         <html:ul id="addon-webext-perm-list" class="addon-webext-perm-list"/>
       </popupnotificationcontent>
     </popupnotification>
 
     <popupnotification id="addon-installed-notification" hidden="true">
-      <popupnotificationcontent orient="vertical">
+      <popupnotificationcontent class="addon-installed-notification-content" orient="vertical">
         <description id="addon-installed-notification-header"/>
         <description id="addon-installed-notification-message"/>
       </popupnotificationcontent>
     </popupnotification>
--- a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-out.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-out.js
@@ -57,16 +57,32 @@ add_task(async function focus() {
   // Check the Change Options link.
   let changeOptionsLink = document.getElementById("search-suggestions-change-settings");
   let prefsPromise = BrowserTestUtils.waitForLocationChange(gBrowser, "about:preferences#general-search");
   changeOptionsLink.click();
   await prefsPromise;
   Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
 });
 
+add_task(async function new_tab() {
+  // Opening a new tab when the urlbar is unfocused, should focusing it and thus
+  // open the popup in order to show the notification.
+  setupVisibleHint();
+  gURLBar.blur();
+  let popupPromise = promisePopupShown(gURLBar.popup);
+  // openNewForegroundTab doesn't focus the urlbar.
+  await BrowserTestUtils.synthesizeKey("t", { accelKey: true }, gBrowser.selectedBrowser);
+  await popupPromise;
+  Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+  assertVisible(true);
+  assertFooterVisible(false);
+  Assert.equal(gURLBar.popup._matchCount, 0, "popup should have no results");
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+});
 
 add_task(async function privateWindow() {
   // Since suggestions are disabled in private windows, the notification should
   // not appear even when suggestions are otherwise enabled.
   setupVisibleHint();
   let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
   await promiseAutocompleteResultPopup("foo", win);
   assertVisible(false, win);
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1158,18 +1158,17 @@ file, You can obtain one at http://mozil
               }
               this.setAttribute("textoverflow", "true");
               break;
             case "underflow":
               this.removeAttribute("textoverflow");
               this._hideURLTooltip();
               break;
             case "TabSelect":
-              this.detachController();
-              this.attachController();
+              this.controller.resetInternalState();
               break;
           }
         ]]></body>
       </method>
 
       <!--
         onBeforeTextValueSet is called by the base-binding's .textValue getter.
         It should return the value that the getter should use.
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -1518,17 +1518,18 @@ var CustomizableUIInternal = {
     } else if (aWidget.type == "view") {
       let ownerWindow = aNode.ownerGlobal;
       let area = this.getPlacementOfWidget(aNode.id).area;
       let areaType = CustomizableUI.getAreaType(area);
       let anchor = aNode;
       if (areaType != CustomizableUI.TYPE_MENU_PANEL) {
         let wrapper = this.wrapWidget(aWidget.id).forWindow(ownerWindow);
 
-        if (wrapper && !wrapper.overflowed && wrapper.anchor) {
+        let hasMultiView = !!aNode.closest("photonpanelmultiview,panelmultiview");
+        if (wrapper && !hasMultiView && wrapper.anchor) {
           this.hidePanelForNode(aNode);
           anchor = wrapper.anchor;
         }
       }
       ownerWindow.PanelUI.showSubView(aWidget.viewId, anchor, area);
     }
   },
 
@@ -1721,19 +1722,23 @@ var CustomizableUIInternal = {
       target = target.parentNode;
     }
     if (closemenu == "none" || widgetType == "view") {
       return;
     }
 
     if (closemenu == "single") {
       let panel = this._getPanelForNode(target);
-      let multiview = panel.querySelector("panelmultiview");
+      let multiview = panel.querySelector("photonpanelmultiview,panelmultiview");
       if (multiview.showingSubView) {
-        multiview.showMainView();
+        if (multiview.instance.panelViews) {
+          multiview.goBack();
+        } else {
+          multiview.showMainView();
+        }
         return;
       }
     }
 
     // If we get here, we can actually hide the popup:
     this.hidePanelForNode(aEvent.target);
   },
 
@@ -4151,19 +4156,25 @@ OverflowableToolbar.prototype = {
 
   show() {
     if (this._panel.state == "open") {
       return Promise.resolve();
     }
     return new Promise(resolve => {
       let doc = this._panel.ownerDocument;
       this._panel.hidden = false;
-      let mainViewId = this._panel.querySelector("panelmultiview").getAttribute("mainViewId");
-      let mainView = doc.getElementById(mainViewId);
-      let contextMenu = doc.getElementById(mainView.getAttribute("context"));
+      let photonView = this._panel.querySelector("panelmultiview");
+      let contextMenu;
+      if (photonView) {
+        let mainViewId = photonView.getAttribute("mainViewId");
+        let mainView = doc.getElementById(mainViewId);
+        contextMenu = doc.getElementById(mainView.getAttribute("context"));
+      } else {
+        contextMenu = doc.getElementById(this._panel.getAttribute("context"));
+      }
       gELS.addSystemEventListener(contextMenu, "command", this, true);
       let anchor = doc.getAnonymousElementByAttribute(this._chevron, "class", "toolbarbutton-icon");
       // Ensure we update the gEditUIVisible flag when opening the popup, in
       // case the edit controls are in it.
       this._panel.addEventListener("popupshowing", () => doc.defaultView.updateEditUIVisibility(), {once: true});
       this._panel.openPopup(anchor || this._chevron);
       this._chevron.open = true;
 
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -335,28 +335,35 @@
   </panelmultiview>
 </panel>
 
 <panel id="widget-overflow"
        role="group"
        type="arrow"
        noautofocus="true"
        position="bottomcenter topright"
+#ifndef MOZ_PHOTON_THEME
+       context="toolbar-context-menu"
+#endif
        hidden="true">
+#ifdef MOZ_PHOTON_THEME
   <panelmultiview mainViewId="widget-overflow-mainView">
     <panelview id="widget-overflow-mainView"
                context="toolbar-context-menu">
+#endif
       <vbox id="widget-overflow-scroller">
         <vbox id="widget-overflow-list" class="widget-overflow-list"
               overflowfortoolbar="nav-bar"/>
         <toolbarseparator id="widget-overflow-fixed-separator" hidden="true"/>
         <vbox id="widget-overflow-fixed-list" class="widget-overflow-list" hidden="true"/>
       </vbox>
+#ifdef MOZ_PHOTON_THEME
     </panelview>
   </panelmultiview>
+#endif
 </panel>
 
 <panel id="customization-tipPanel"
        type="arrow"
        flip="none"
        side="left"
        position="leftcenter topright"
        noautohide="true"
--- a/browser/components/customizableui/test/browser_overflow_use_subviews.js
+++ b/browser/components/customizableui/test/browser_overflow_use_subviews.js
@@ -36,18 +36,20 @@ add_task(async function check_developer_
   chevron.click();
   await shownPanelPromise;
 
   let developerView = document.getElementById("PanelUI-developer");
   let button = document.getElementById("developer-button");
   let subviewShownPromise = subviewShown(developerView);
   button.click();
   await subviewShownPromise;
-  is(developerView.closest("panel"), kOverflowPanel, "Should be inside the panel");
-  kOverflowPanel.hidePopup();
+  let hasSubviews = !!kOverflowPanel.querySelector("photonpanelmultiview,panelmultiview");
+  let expectedPanel = hasSubviews ? kOverflowPanel : document.getElementById("customizationui-widget-panel");
+  is(developerView.closest("panel"), expectedPanel, "Should be inside the panel");
+  expectedPanel.hidePopup();
   await Promise.resolve(); // wait for popup to hide fully.
 });
 
 /**
  * This checks that non-subview-compatible items still work correctly.
  * Ideally we should make the downloads panel and bookmarks/library item
  * proper subview items, then this test can go away, and potentially we can
  * simplify some of the subview anchoring code.
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -36,16 +36,18 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AppMenuNotifications",
                                   "resource://gre/modules/AppMenuNotifications.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
                                   "resource://gre/modules/DownloadUIHelper.jsm");
@@ -421,18 +423,22 @@ this.DownloadsCommon = {
     }
     if (aMimeInfo && !(aMimeInfo instanceof Ci.nsIMIMEInfo)) {
       throw new Error("Invalid value passed for aMimeInfo");
     }
     if (!(aOwnerWindow instanceof Ci.nsIDOMWindow)) {
       throw new Error("aOwnerWindow must be a dom-window object");
     }
 
+    let isWindowsExe = AppConstants.platform == "win" &&
+      aFile.leafName.toLowerCase().endsWith(".exe");
+
     let promiseShouldLaunch;
-    if (aFile.isExecutable()) {
+    // Don't prompt on Windows for .exe since there will be a native prompt.
+    if (aFile.isExecutable() && !isWindowsExe) {
       // We get a prompter for the provided window here, even though anchoring
       // to the most recently active window should work as well.
       promiseShouldLaunch =
         DownloadUIHelper.getPrompter(aOwnerWindow)
                         .confirmLaunchExecutable(aFile.path);
     } else {
       promiseShouldLaunch = Promise.resolve(true);
     }
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -110,24 +110,16 @@ extensions.registerModules({
     url: "chrome://browser/content/ext-commands.js",
     schema: "chrome://browser/content/schemas/commands.json",
     scopes: ["addon_parent"],
     manifest: ["commands"],
     paths: [
       ["commands"],
     ],
   },
-  contextMenus: {
-    url: "chrome://browser/content/ext-contextMenus.js",
-    schema: "chrome://browser/content/schemas/context_menus.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["contextMenus"],
-    ],
-  },
   devtools: {
     url: "chrome://browser/content/ext-devtools.js",
     schema: "chrome://browser/content/schemas/devtools.json",
     scopes: ["devtools_parent"],
     manifest: ["devtools_page"],
     paths: [
       ["devtools"],
     ],
@@ -159,16 +151,26 @@ extensions.registerModules({
   history: {
     url: "chrome://browser/content/ext-history.js",
     schema: "chrome://browser/content/schemas/history.json",
     scopes: ["addon_parent"],
     paths: [
       ["history"],
     ],
   },
+  // This module supports the "menus" and "contextMenus" namespaces,
+  // and because of permissions, the module name must differ from both.
+  menusInternal: {
+    url: "chrome://browser/content/ext-menus.js",
+    schema: "chrome://browser/content/schemas/menus.json",
+    scopes: ["addon_parent"],
+    paths: [
+      ["menusInternal"],
+    ],
+  },
   omnibox: {
     url: "chrome://browser/content/ext-omnibox.js",
     schema: "chrome://browser/content/schemas/omnibox.json",
     scopes: ["addon_parent"],
     manifest: ["omnibox"],
     paths: [
       ["omnibox"],
     ],
--- a/browser/components/extensions/ext-c-browser.js
+++ b/browser/components/extensions/ext-c-browser.js
@@ -17,21 +17,23 @@ extensions.registerModules({
   },
   devtools_panels: {
     url: "chrome://browser/content/ext-c-devtools-panels.js",
     scopes: ["devtools_child"],
     paths: [
       ["devtools", "panels"],
     ],
   },
-  contextMenus: {
-    url: "chrome://browser/content/ext-c-contextMenus.js",
+  // Because of permissions, the module name must differ from both namespaces.
+  menusInternal: {
+    url: "chrome://browser/content/ext-c-menus.js",
     scopes: ["addon_child"],
     paths: [
       ["contextMenus"],
+      ["menus"],
     ],
   },
   omnibox: {
     url: "chrome://browser/content/ext-c-omnibox.js",
     scopes: ["addon_child"],
     paths: [
       ["omnibox"],
     ],
rename from browser/components/extensions/ext-c-contextMenus.js
rename to browser/components/extensions/ext-c-menus.js
--- a/browser/components/extensions/ext-c-contextMenus.js
+++ b/browser/components/extensions/ext-c-menus.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ../../../toolkit/components/extensions/ext-c-toolkit.js */
+
 // If id is not specified for an item we use an integer.
 // This ID need only be unique within a single addon. Since all addon code that
 // can use this API runs in the same process, this local variable suffices.
 var gNextMenuItemID = 0;
 
 // Map[Extension -> Map[string or id, ContextMenusClickPropHandler]]
 var gPropHandlers = new Map();
 
@@ -31,17 +34,17 @@ class ContextMenusClickPropHandler {
       onclick(info, tab);
     }
   }
 
   // Sets the `onclick` handler for the given menu item.
   // The `onclick` function MUST be owned by `this.context`.
   setListener(id, onclick) {
     if (this.onclickMap.size === 0) {
-      this.context.childManager.getParentEvent("contextMenus.onClicked").addListener(this.dispatchEvent);
+      this.context.childManager.getParentEvent("menusInternal.onClicked").addListener(this.dispatchEvent);
       this.context.callOnClose(this);
     }
     this.onclickMap.set(id, onclick);
 
     let propHandlerMap = gPropHandlers.get(this.context.extension);
     if (!propHandlerMap) {
       propHandlerMap = new Map();
     } else {
@@ -58,17 +61,17 @@ class ContextMenusClickPropHandler {
 
   // Deletes the `onclick` handler for the given menu item.
   // The `onclick` function MUST be owned by `this.context`.
   unsetListener(id) {
     if (!this.onclickMap.delete(id)) {
       return;
     }
     if (this.onclickMap.size === 0) {
-      this.context.childManager.getParentEvent("contextMenus.onClicked").removeListener(this.dispatchEvent);
+      this.context.childManager.getParentEvent("menusInternal.onClicked").removeListener(this.dispatchEvent);
       this.context.forgetOnClose(this);
     }
     let propHandlerMap = gPropHandlers.get(this.context.extension);
     propHandlerMap.delete(id);
     if (propHandlerMap.size === 0) {
       gPropHandlers.delete(this.context.extension);
     }
   }
@@ -96,65 +99,86 @@ class ContextMenusClickPropHandler {
   // Removes all `onclick` handlers from this context.
   close() {
     for (let id of this.onclickMap.keys()) {
       this.unsetListener(id);
     }
   }
 }
 
-this.contextMenus = class extends ExtensionAPI {
+this.menusInternal = class extends ExtensionAPI {
   getAPI(context) {
     let onClickedProp = new ContextMenusClickPropHandler(context);
 
-    return {
-      contextMenus: {
+    let api = {
+      menus: {
         create(createProperties, callback) {
           if (createProperties.id === null) {
             createProperties.id = ++gNextMenuItemID;
           }
           let {onclick} = createProperties;
           delete createProperties.onclick;
-          context.childManager.callParentAsyncFunction("contextMenus.createInternal", [
+          context.childManager.callParentAsyncFunction("menusInternal.create", [
             createProperties,
           ]).then(() => {
             if (onclick) {
               onClickedProp.setListener(createProperties.id, onclick);
             }
             if (callback) {
               callback();
             }
           });
           return createProperties.id;
         },
 
         update(id, updateProperties) {
           let {onclick} = updateProperties;
           delete updateProperties.onclick;
-          return context.childManager.callParentAsyncFunction("contextMenus.update", [
+          return context.childManager.callParentAsyncFunction("menusInternal.update", [
             id,
             updateProperties,
           ]).then(() => {
             if (onclick) {
               onClickedProp.setListener(id, onclick);
             } else if (onclick === null) {
               onClickedProp.unsetListenerFromAnyContext(id);
             }
             // else onclick is not set so it should not be changed.
           });
         },
 
         remove(id) {
           onClickedProp.unsetListenerFromAnyContext(id);
-          return context.childManager.callParentAsyncFunction("contextMenus.remove", [
+          return context.childManager.callParentAsyncFunction("menusInternal.remove", [
             id,
           ]);
         },
 
         removeAll() {
           onClickedProp.deleteAllListenersFromExtension();
 
-          return context.childManager.callParentAsyncFunction("contextMenus.removeAll", []);
+          return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []);
         },
+
+        onClicked: new SingletonEventManager(context, "menus.onClicked", fire => {
+          let listener = (info, tab) => {
+            fire.async(info, tab);
+          };
+
+          let event = context.childManager.getParentEvent("menusInternal.onClicked");
+          event.addListener(listener);
+          return () => {
+            event.removeListener(listener);
+          };
+        }).api(),
       },
     };
+
+    const result = {};
+    if (context.extension.hasPermission("menus")) {
+      result.menus = api.menus;
+    }
+    if (context.extension.hasPermission("contextMenus")) {
+      result.contextMenus = api.menus;
+    }
+    return result;
   }
 };
rename from browser/components/extensions/ext-contextMenus.js
rename to browser/components/extensions/ext-menus.js
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-menus.js
@@ -20,32 +20,32 @@ var {
   IconDetails,
 } = ExtensionParent;
 
 const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
 
 // Map[Extension -> Map[ID -> MenuItem]]
 // Note: we want to enumerate all the menu items so
 // this cannot be a weak map.
-var gContextMenuMap = new Map();
+var gMenuMap = new Map();
 
 // Map[Extension -> MenuItem]
 var gRootItems = new Map();
 
 // If id is not specified for an item we use an integer.
 var gNextMenuItemID = 0;
 
 // Used to assign unique names to radio groups.
 var gNextRadioGroupID = 0;
 
 // The max length of a menu item's label.
 var gMaxLabelLength = 64;
 
 var gMenuBuilder = {
-  // When a new contextMenu is opened, this function is called and
+  // When a new menu is opened, this function is called and
   // we populate the |xulMenu| with all the items from extensions
   // to be displayed. We always clear all the items again when
   // popuphidden fires.
   build(contextData) {
     let firstItem = true;
     let xulMenu = contextData.menu;
     xulMenu.addEventListener("popuphidden", this);
     this.xulMenu = xulMenu;
@@ -253,29 +253,29 @@ var gMenuBuilder = {
       let info = item.getClickInfo(contextData, wasChecked);
 
       const map = {shiftKey: "Shift", altKey: "Alt", metaKey: "Command", ctrlKey: "Ctrl"};
       info.modifiers = Object.keys(map).filter(key => event[key]).map(key => map[key]);
       if (event.ctrlKey && AppConstants.platform === "macosx") {
         info.modifiers.push("MacCtrl");
       }
 
-      // Allow context menu's to open various actions supported in webext prior
+      // Allow menus to open various actions supported in webext prior
       // to notifying onclicked.
       let actionFor = {
         _execute_page_action: global.pageActionFor,
         _execute_browser_action: global.browserActionFor,
         _execute_sidebar_action: global.sidebarActionFor,
       }[item.command];
       if (actionFor) {
         let win = event.target.ownerGlobal;
         actionFor(item.extension).triggerAction(win);
       }
 
-      item.extension.emit("webext-contextmenu-menuitem-click", info, tab);
+      item.extension.emit("webext-menu-menuitem-click", info, tab);
     });
 
     return element;
   },
 
   handleEvent(event) {
     if (this.xulMenu != event.target || event.type != "popuphidden") {
       return;
@@ -409,32 +409,32 @@ MenuItem.prototype = {
       enabled: true,
     });
   },
 
   set id(id) {
     if (this.hasOwnProperty("_id")) {
       throw new Error("Id of a MenuItem cannot be changed");
     }
-    let isIdUsed = gContextMenuMap.get(this.extension).has(id);
+    let isIdUsed = gMenuMap.get(this.extension).has(id);
     if (isIdUsed) {
       throw new Error("Id already exists");
     }
     this._id = id;
   },
 
   get id() {
     return this._id;
   },
 
   ensureValidParentId(parentId) {
     if (parentId === undefined) {
       return;
     }
-    let menuMap = gContextMenuMap.get(this.extension);
+    let menuMap = gMenuMap.get(this.extension);
     if (!menuMap.has(parentId)) {
       throw new Error("Could not find any MenuItem with id: " + parentId);
     }
     for (let item = menuMap.get(parentId); item; item = item.parent) {
       if (item === this) {
         throw new ExtensionError("MenuItem cannot be an ancestor (or self) of its new parent.");
       }
     }
@@ -445,17 +445,17 @@ MenuItem.prototype = {
 
     if (this.parent) {
       this.parent.detachChild(this);
     }
 
     if (parentId === undefined) {
       this.root.addChild(this);
     } else {
-      let menuMap = gContextMenuMap.get(this.extension);
+      let menuMap = gMenuMap.get(this.extension);
       menuMap.get(parentId).addChild(this);
     }
   },
 
   get parentId() {
     return this.parent ? this.parent.id : undefined;
   },
 
@@ -492,17 +492,17 @@ MenuItem.prototype = {
     if (this.parent) {
       this.parent.detachChild(this);
     }
     let children = this.children.slice(0);
     for (let child of children) {
       child.remove();
     }
 
-    let menuMap = gContextMenuMap.get(this.extension);
+    let menuMap = gMenuMap.get(this.extension);
     menuMap.delete(this.id);
     if (this.root == this) {
       gRootItems.delete(this.extension);
     }
   },
 
   getClickInfo(contextData, wasChecked) {
     let mediaType;
@@ -572,17 +572,17 @@ MenuItem.prototype = {
     }
 
     return true;
   },
 };
 
 // While any extensions are active, this Tracker registers to observe/listen
 // for contex-menu events from both content and chrome.
-const contextMenuTracker = {
+const menuTracker = {
   register() {
     Services.obs.addObserver(this, "on-build-contextmenu");
     for (const window of windowTracker.browserWindows()) {
       this.onWindowOpen(window);
     }
     windowTracker.addOpenListener(this.onWindowOpen);
   },
 
@@ -597,90 +597,92 @@ const contextMenuTracker = {
 
   observe(subject, topic, data) {
     subject = subject.wrappedJSObject;
     gMenuBuilder.build(subject);
   },
 
   onWindowOpen(window) {
     const menu = window.document.getElementById("tabContextMenu");
-    menu.addEventListener("popupshowing", contextMenuTracker);
+    menu.addEventListener("popupshowing", menuTracker);
   },
 
   handleEvent(event) {
     const menu = event.target;
     if (menu.id === "tabContextMenu") {
       const trigger = menu.triggerNode;
       const tab = trigger.localName === "tab" ? trigger : tabTracker.activeTab;
       const pageUrl = tab.linkedBrowser.currentURI.spec;
       gMenuBuilder.build({menu, tab, pageUrl, onTab: true});
     }
   },
 };
 
-var gExtensionCount = 0;
+this.menusInternal = class extends ExtensionAPI {
+  constructor(extension) {
+    super(extension);
 
-this.contextMenus = class extends ExtensionAPI {
+    if (!gMenuMap.size) {
+      menuTracker.register();
+    }
+    gMenuMap.set(extension, new Map());
+  }
+
   onShutdown(reason) {
     let {extension} = this;
 
-    if (gContextMenuMap.has(extension)) {
-      gContextMenuMap.delete(extension);
+    if (gMenuMap.has(extension)) {
+      gMenuMap.delete(extension);
       gRootItems.delete(extension);
-      if (--gExtensionCount == 0) {
-        contextMenuTracker.unregister();
+      if (!gMenuMap.size) {
+        menuTracker.unregister();
       }
     }
   }
 
   getAPI(context) {
     let {extension} = context;
 
-    gContextMenuMap.set(extension, new Map());
-    if (++gExtensionCount == 1) {
-      contextMenuTracker.register();
-    }
-
     return {
-      contextMenus: {
-        createInternal: function(createProperties) {
+      menusInternal: {
+        create: function(createProperties) {
           // Note that the id is required by the schema. If the addon did not set
-          // it, the implementation of contextMenus.create in the child should
+          // it, the implementation of menus.create in the child should
           // have added it.
           let menuItem = new MenuItem(extension, createProperties);
-          gContextMenuMap.get(extension).set(menuItem.id, menuItem);
+          gMenuMap.get(extension).set(menuItem.id, menuItem);
         },
 
         update: function(id, updateProperties) {
-          let menuItem = gContextMenuMap.get(extension).get(id);
+          let menuItem = gMenuMap.get(extension).get(id);
           if (menuItem) {
             menuItem.setProps(updateProperties);
           }
         },
 
         remove: function(id) {
-          let menuItem = gContextMenuMap.get(extension).get(id);
+          let menuItem = gMenuMap.get(extension).get(id);
           if (menuItem) {
             menuItem.remove();
           }
         },
 
         removeAll: function() {
           let root = gRootItems.get(extension);
           if (root) {
             root.remove();
           }
         },
 
-        onClicked: new SingletonEventManager(context, "contextMenus.onClicked", fire => {
+        onClicked: new SingletonEventManager(context, "menusInternal.onClicked", fire => {
           let listener = (event, info, tab) => {
             fire.async(info, tab);
           };
 
-          extension.on("webext-contextmenu-menuitem-click", listener);
+          extension.on("webext-menu-menuitem-click", listener);
           return () => {
-            extension.off("webext-contextmenu-menuitem-click", listener);
+            extension.off("webext-menu-menuitem-click", listener);
           };
         }).api(),
       },
     };
   }
 };
--- a/browser/components/extensions/extensions-browser.manifest
+++ b/browser/components/extensions/extensions-browser.manifest
@@ -1,6 +1,6 @@
 category webextension-scripts browser chrome://browser/content/ext-browser.js
 category webextension-scripts utils chrome://browser/content/ext-utils.js
 category webextension-scripts-devtools browser chrome://browser/content/ext-c-browser.js
 category webextension-scripts-addon browser chrome://browser/content/ext-c-browser.js
 
-category webextension-schemas context_menus_internal chrome://browser/content/schemas/context_menus_internal.json
+category webextension-schemas menus_internal chrome://browser/content/schemas/menus_internal.json
--- a/browser/components/extensions/jar.mn
+++ b/browser/components/extensions/jar.mn
@@ -13,30 +13,30 @@ browser.jar:
 #endif
     content/browser/extension.svg
     content/browser/ext-bookmarks.js
     content/browser/ext-browser.js
     content/browser/ext-browserAction.js
     content/browser/ext-browsingData.js
     content/browser/ext-chrome-settings-overrides.js
     content/browser/ext-commands.js
-    content/browser/ext-contextMenus.js
     content/browser/ext-devtools.js
     content/browser/ext-devtools-inspectedWindow.js
     content/browser/ext-devtools-network.js
     content/browser/ext-devtools-panels.js
     content/browser/ext-geckoProfiler.js
     content/browser/ext-history.js
+    content/browser/ext-menus.js
     content/browser/ext-omnibox.js
     content/browser/ext-pageAction.js
     content/browser/ext-sessions.js
     content/browser/ext-sidebarAction.js
     content/browser/ext-tabs.js
     content/browser/ext-url-overrides.js
     content/browser/ext-utils.js
     content/browser/ext-windows.js
     content/browser/ext-c-browser.js
-    content/browser/ext-c-contextMenus.js
     content/browser/ext-c-devtools-inspectedWindow.js
     content/browser/ext-c-devtools-panels.js
     content/browser/ext-c-devtools.js
+    content/browser/ext-c-menus.js
     content/browser/ext-c-omnibox.js
     content/browser/ext-c-tabs.js
--- a/browser/components/extensions/schemas/jar.mn
+++ b/browser/components/extensions/schemas/jar.mn
@@ -3,23 +3,23 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
     content/browser/schemas/bookmarks.json
     content/browser/schemas/browser_action.json
     content/browser/schemas/browsing_data.json
     content/browser/schemas/chrome_settings_overrides.json
     content/browser/schemas/commands.json
-    content/browser/schemas/context_menus.json
-    content/browser/schemas/context_menus_internal.json
     content/browser/schemas/devtools.json
     content/browser/schemas/devtools_inspected_window.json
     content/browser/schemas/devtools_network.json
     content/browser/schemas/devtools_panels.json
     content/browser/schemas/geckoProfiler.json
     content/browser/schemas/history.json
+    content/browser/schemas/menus.json
+    content/browser/schemas/menus_internal.json
     content/browser/schemas/omnibox.json
     content/browser/schemas/page_action.json
     content/browser/schemas/sessions.json
     content/browser/schemas/sidebar_action.json
     content/browser/schemas/tabs.json
     content/browser/schemas/url_overrides.json
     content/browser/schemas/windows.json
rename from browser/components/extensions/schemas/context_menus.json
rename to browser/components/extensions/schemas/menus.json
--- a/browser/components/extensions/schemas/context_menus.json
+++ b/browser/components/extensions/schemas/menus.json
@@ -6,26 +6,33 @@
   {
     "namespace": "manifest",
     "types": [
       {
         "$extend": "Permission",
         "choices": [{
           "type": "string",
           "enum": [
+            "menus",
             "contextMenus"
           ]
         }]
       }
     ]
   },
   {
     "namespace": "contextMenus",
-    "description": "Use the <code>browser.contextMenus</code> API to add items to the browser's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
     "permissions": ["contextMenus"],
+    "description": "Use the browser.contextMenus API to add items to the browser's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
+    "$import": "menus"
+  },
+  {
+    "namespace": "menus",
+    "permissions": ["menus"],
+    "description": "Use the browser.menus API to add items to the browser's menus. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
     "properties": {
       "ACTION_MENU_TOP_LEVEL_LIMIT": {
         "value": 6,
         "description": "The maximum number of top level extension items that can be added to an extension action context menu. Any items beyond this limit will be ignored."
       }
     },
     "types": [
       {
@@ -259,17 +266,17 @@
                 "optional": true
               },
               "onclick": {
                 "type": "function",
                 "optional": "omit-key-if-missing",
                 "parameters": [
                   {
                     "name": "info",
-                    "$ref": "contextMenusInternal.OnClickData"
+                    "$ref": "menusInternal.OnClickData"
                   },
                   {
                     "name": "tab",
                     "$ref": "tabs.Tab",
                     "description": "The details of the tab where the click took place. Note: this parameter only present for extensions."
                   }
                 ]
               },
rename from browser/components/extensions/schemas/context_menus_internal.json
rename to browser/components/extensions/schemas/menus_internal.json
--- a/browser/components/extensions/schemas/context_menus_internal.json
+++ b/browser/components/extensions/schemas/menus_internal.json
@@ -1,15 +1,16 @@
 // Copyright 2014 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 [
   {
-    "namespace": "contextMenusInternal",
+    "namespace": "menusInternal",
+    "allowedContexts": ["addon_parent_only"],
     "description": "Use the <code>browser.contextMenus</code> API to add items to the browser's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
     "types": [
       {
         "id": "OnClickData",
         "type": "object",
         "description": "Information sent when a context menu item is clicked.",
         "properties": {
           "menuItemId": {
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -47,17 +47,16 @@ skip-if = (os == 'win' && !debug) # bug 
 [browser_ext_commands_execute_browser_action.js]
 [browser_ext_commands_execute_page_action.js]
 [browser_ext_commands_execute_sidebar_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_contextMenus.js]
 [browser_ext_contextMenus_checkboxes.js]
-[browser_ext_contextMenus_chrome.js]
 [browser_ext_contextMenus_commands.js]
 [browser_ext_contextMenus_icons.js]
 [browser_ext_contextMenus_onclick.js]
 [browser_ext_contextMenus_radioGroups.js]
 [browser_ext_contextMenus_uninstall.js]
 [browser_ext_contextMenus_urlPatterns.js]
 [browser_ext_currentWindow.js]
 [browser_ext_devtools_inspectedWindow.js]
@@ -67,16 +66,17 @@ skip-if = (os == 'win' && !debug) # bug 
 [browser_ext_devtools_page.js]
 [browser_ext_devtools_panel.js]
 [browser_ext_geckoProfiler_symbolicate.js]
 [browser_ext_getViews.js]
 [browser_ext_identity_indication.js]
 [browser_ext_incognito_views.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
+[browser_ext_menus.js]
 [browser_ext_omnibox.js]
 skip-if = debug || asan # Bug 1354681
 [browser_ext_optionsPage_browser_style.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
 [browser_ext_pageAction_contextMenu.js]
 [browser_ext_pageAction_popup.js]
 [browser_ext_pageAction_popup_resize.js]
rename from browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
rename to browser/components/extensions/test/browser/browser_ext_menus.js
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
+++ b/browser/components/extensions/test/browser/browser_ext_menus.js
@@ -1,32 +1,64 @@
 /* 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 PAGE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html";
+
+add_task(async function test_permissions() {
+  function background() {
+    browser.test.sendMessage("apis", {
+      menus: typeof browser.menus,
+      contextMenus: typeof browser.contextMenus,
+      menusInternal: typeof browser.menusInternal,
+    });
+  }
+
+  const first = ExtensionTestUtils.loadExtension({manifest: {permissions: ["menus"]}, background});
+  const second = ExtensionTestUtils.loadExtension({manifest: {permissions: ["contextMenus"]}, background});
+
+  await first.startup();
+  await second.startup();
+
+  const apis1 = await first.awaitMessage("apis");
+  const apis2 = await second.awaitMessage("apis");
+
+  is(apis1.menus, "object", "browser.menus available with 'menus' permission");
+  is(apis1.contextMenus, "undefined", "browser.contextMenus unavailable with  'menus' permission");
+  is(apis1.menusInternal, "undefined", "browser.menusInternal is never available");
+
+  is(apis2.menus, "undefined", "browser.menus unavailable with 'contextMenus' permission");
+  is(apis2.contextMenus, "object", "browser.contextMenus unavailable with  'contextMenus' permission");
+  is(apis2.menusInternal, "undefined", "browser.menusInternal is never available");
+
+  await first.unload();
+  await second.unload();
+});
+
 add_task(async function test_actionContextMenus() {
   const manifest = {
     page_action: {},
     browser_action: {},
-    permissions: ["contextMenus"],
+    permissions: ["menus"],
   };
 
   async function background() {
     const contexts = ["page_action", "browser_action"];
 
-    const parentId = browser.contextMenus.create({contexts, title: "parent"});
-    await browser.contextMenus.create({parentId, title: "click A"});
-    await browser.contextMenus.create({parentId, title: "click B"});
+    const parentId = browser.menus.create({contexts, title: "parent"});
+    await browser.menus.create({parentId, title: "click A"});
+    await browser.menus.create({parentId, title: "click B"});
 
     for (let i = 1; i < 9; i++) {
-      await browser.contextMenus.create({contexts, id: `${i}`, title: `click ${i}`});
+      await browser.menus.create({contexts, id: `${i}`, title: `click ${i}`});
     }
 
-    browser.contextMenus.onClicked.addListener((info, tab) => {
+    browser.menus.onClicked.addListener((info, tab) => {
       browser.test.sendMessage("click", {info, tab});
     });
 
     const [tab] = await browser.tabs.query({active: true});
     await browser.pageAction.show(tab.id);
     browser.test.sendMessage("ready", tab.id);
   }
 
@@ -65,43 +97,43 @@ add_task(async function test_actionConte
 
   await BrowserTestUtils.removeTab(tab);
   await extension.unload();
 });
 
 add_task(async function test_tabContextMenu() {
   const first = ExtensionTestUtils.loadExtension({
     manifest: {
-      permissions: ["contextMenus"],
+      permissions: ["menus"],
     },
     async background() {
-      await browser.contextMenus.create({
+      await browser.menus.create({
         id: "alpha-beta-parent", title: "alpha-beta parent", contexts: ["tab"],
       });
 
-      await browser.contextMenus.create({parentId: "alpha-beta-parent", title: "alpha"});
-      await browser.contextMenus.create({parentId: "alpha-beta-parent", title: "beta"});
+      await browser.menus.create({parentId: "alpha-beta-parent", title: "alpha"});
+      await browser.menus.create({parentId: "alpha-beta-parent", title: "beta"});
 
-      await browser.contextMenus.create({title: "dummy", contexts: ["page"]});
+      await browser.menus.create({title: "dummy", contexts: ["page"]});
 
-      browser.contextMenus.onClicked.addListener((info, tab) => {
+      browser.menus.onClicked.addListener((info, tab) => {
         browser.test.sendMessage("click", {info, tab});
       });
 
       const [tab] = await browser.tabs.query({active: true});
       browser.test.sendMessage("ready", tab.id);
     },
   });
 
   const second = ExtensionTestUtils.loadExtension({
     manifest: {
-      permissions: ["contextMenus"],
+      permissions: ["menus"],
     },
     async background() {
-      await browser.contextMenus.create({title: "gamma", contexts: ["tab"]});
+      await browser.menus.create({title: "gamma", contexts: ["tab"]});
       browser.test.sendMessage("ready");
     },
   });
 
   const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
   await first.startup();
   await second.startup();
 
@@ -138,30 +170,29 @@ add_task(async function test_tabContextM
 
   await BrowserTestUtils.removeTab(tab);
   await first.unload();
   await second.unload();
 });
 
 add_task(async function test_onclick_frameid() {
   const manifest = {
-    permissions: ["contextMenus"],
+    permissions: ["menus"],
   };
 
   function background() {
     function onclick(info) {
       browser.test.sendMessage("click", info);
     }
-    browser.contextMenus.create({contexts: ["frame", "page"], title: "modify", onclick});
+    browser.menus.create({contexts: ["frame", "page"], title: "modify", onclick});
     browser.test.sendMessage("ready");
   }
 
   const extension = ExtensionTestUtils.loadExtension({manifest, background});
-  const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser,
-    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+  const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
 
   await extension.startup();
   await extension.awaitMessage("ready");
 
   async function click(selectorOrId) {
     const func = (selectorOrId == "body") ? openContextMenu : openContextMenuInFrame;
     const menu = await func(selectorOrId);
     const items = menu.getElementsByAttribute("label", "modify");
@@ -173,8 +204,54 @@ add_task(async function test_onclick_fra
   is(info.frameId, 0, "top level click");
   info = await click("frame");
   isnot(info.frameId, undefined, "frame click, frameId is not undefined");
   isnot(info.frameId, 0, "frame click, frameId probably okay");
 
   await BrowserTestUtils.removeTab(tab);
   await extension.unload();
 });
+
+add_task(async function test_multiple_contexts_init() {
+  const manifest = {
+    permissions: ["menus"],
+  };
+
+  function background() {
+    browser.menus.create({id: "parent", title: "parent"});
+    browser.tabs.create({url: "tab.html", active: false});
+  }
+
+  const files = {
+    "tab.html": "<!DOCTYPE html><meta charset=utf-8><script src=tab.js></script>",
+    "tab.js": function() {
+      browser.menus.create({parentId: "parent", id: "child", title: "child"});
+
+      browser.menus.onClicked.addListener(info => {
+        browser.test.sendMessage("click", info);
+      });
+
+      browser.test.sendMessage("ready");
+    },
+  };
+
+  const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+  const extension = ExtensionTestUtils.loadExtension({manifest, background, files});
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  const menu = await openContextMenu();
+  const items = menu.getElementsByAttribute("label", "parent");
+
+  is(items.length, 1, "Found parent menu item");
+  is(items[0].tagName, "menu", "And it has children");
+
+  const popup = await openSubmenu(items[0]);
+  is(popup.firstChild.label, "child", "Correct child menu item");
+  await closeExtensionContextMenu(popup.firstChild);
+
+  const info = await extension.awaitMessage("click");
+  is(info.menuItemId, "child", "onClicked the correct item");
+
+  await BrowserTestUtils.removeTab(tab);
+  await extension.unload();
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -304,17 +304,17 @@ async function openExtensionContextMenu(
   await popupShownPromise;
   return extensionMenu;
 }
 
 async function closeExtensionContextMenu(itemToSelect, modifiers = {}) {
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
   EventUtils.synthesizeMouseAtCenter(itemToSelect, modifiers);
-  await popupHiddenPromise;
+  return popupHiddenPromise;
 }
 
 async function openChromeContextMenu(menuId, target, win = window) {
   const node = win.document.querySelector(target);
   const menu = win.document.getElementById(menuId);
   const shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(node, {type: "contextmenu"}, win);
   await shown;
--- a/browser/components/preferences/in-content-new/main.js
+++ b/browser/components/preferences/in-content-new/main.js
@@ -213,16 +213,31 @@ var gMainPane = {
     }
 
     // Notify observers that the UI is now ready
     Components.classes["@mozilla.org/observer-service;1"]
               .getService(Components.interfaces.nsIObserverService)
               .notifyObservers(window, "main-pane-loaded");
   },
 
+  isE10SEnabled() {
+    let e10sEnabled;
+    try {
+      let e10sStatus = Components.classes["@mozilla.org/supports-PRUint64;1"]
+                         .createInstance(Ci.nsISupportsPRUint64);
+      let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver);
+      appinfo.observe(e10sStatus, "getE10SBlocked", "");
+      e10sEnabled = e10sStatus.data < 2;
+    } catch (e) {
+      e10sEnabled = false;
+    }
+
+    return e10sEnabled;
+  },
+
   enableE10SChange() {
     if (AppConstants.E10S_TESTING_ONLY) {
       let e10sCheckbox = document.getElementById("e10sAutoStart");
       let e10sPref = document.getElementById("browser.tabs.remote.autostart");
       let e10sTempPref = document.getElementById("e10sTempPref");
 
       let prefsToChange;
       if (e10sCheckbox.checked) {
@@ -820,24 +835,36 @@ var gMainPane = {
       accelerationPref.value = accelerationPref.defaultValue;
       performanceSettings.hidden = true;
     } else {
       performanceSettings.hidden = false;
     }
   },
 
   buildContentProcessCountMenuList() {
-    let processCountPref = document.getElementById("dom.ipc.processCount");
-    let bundlePreferences = document.getElementById("bundlePreferences");
-    let label = bundlePreferences.getFormattedString("defaultContentProcessCount",
-      [processCountPref.defaultValue]);
-    let contentProcessCount =
-      document.querySelector(`#contentProcessCount > menupopup >
-                              menuitem[value="${processCountPref.defaultValue}"]`);
-    contentProcessCount.label = label;
+    if (gMainPane.isE10SEnabled()) {
+      let processCountPref = document.getElementById("dom.ipc.processCount");
+      let bundlePreferences = document.getElementById("bundlePreferences");
+      let label = bundlePreferences.getFormattedString("defaultContentProcessCount",
+        [processCountPref.defaultValue]);
+      let contentProcessCount =
+        document.querySelector(`#contentProcessCount > menupopup >
+                                menuitem[value="${processCountPref.defaultValue}"]`);
+      contentProcessCount.label = label;
+
+      document.getElementById("limitContentProcess").disabled = false;
+      document.getElementById("contentProcessCount").disabled = false;
+      document.getElementById("contentProcessCountEnabledDescription").hidden = false;
+      document.getElementById("contentProcessCountDisabledDescription").hidden = true;
+    } else {
+      document.getElementById("limitContentProcess").disabled = true;
+      document.getElementById("contentProcessCount").disabled = true;
+      document.getElementById("contentProcessCountEnabledDescription").hidden = true;
+      document.getElementById("contentProcessCountDisabledDescription").hidden = false;
+    }
   },
 
   buildDefaultEngineDropDown() {
     // This is called each time something affects the list of engines.
     let list = document.getElementById("defaultEngine");
     // Set selection to the current default engine.
     let currentEngine = Services.search.currentEngine.name;
 
--- a/browser/components/preferences/in-content-new/main.xul
+++ b/browser/components/preferences/in-content-new/main.xul
@@ -653,11 +653,12 @@
           <menuitem label="3" value="3"/>
           <menuitem label="4" value="4"/>
           <menuitem label="5" value="5"/>
           <menuitem label="6" value="6"/>
           <menuitem label="7" value="7"/>
         </menupopup>
       </menulist>
     </hbox>
-    <description>&limitContentProcessOption.description;</description>
+    <description id="contentProcessCountEnabledDescription">&limitContentProcessOption.description;</description>
+    <description id="contentProcessCountDisabledDescription">&limitContentProcessOption.disabledDescription;<label class="text-link" href="https://wiki.mozilla.org/Electrolysis">&limitContentProcessOption.disabledDescriptionLink;</label></description>
   </vbox>
 </groupbox>
--- a/browser/components/preferences/in-content-new/tests/browser.ini
+++ b/browser/components/preferences/in-content-new/tests/browser.ini
@@ -34,16 +34,19 @@ skip-if = os != "win" # This test tests 
 [browser_defaultbrowser_alwayscheck.js]
 [browser_healthreport.js]
 skip-if = true || !healthreport # Bug 1185403 for the "true"
 [browser_homepages_filter_aboutpreferences.js]
 [browser_layersacceleration.js]
 [browser_masterpassword.js]
 [browser_notifications_do_not_disturb.js]
 [browser_performance.js]
+skip-if = !e10s
+[browser_performance_non_e10s.js]
+skip-if = e10s
 [browser_permissions_urlFieldHidden.js]
 [browser_proxy_backup.js]
 [browser_privacypane_1.js]
 [browser_privacypane_3.js]
 [browser_privacypane_4.js]
 [browser_privacypane_5.js]
 [browser_privacypane_8.js]
 [browser_sanitizeOnShutdown_prefLocked.js]
--- a/browser/components/preferences/in-content-new/tests/browser_performance.js
+++ b/browser/components/preferences/in-content-new/tests/browser_performance.js
@@ -31,19 +31,26 @@ add_task(async function() {
 
   let allowHWAccel = doc.querySelector("#allowHWAccel");
   let allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, DEFAULT_HW_ACCEL_PREF,
     "pref value should be the default value before clicking on checkbox");
   is(allowHWAccel.checked, !DEFAULT_HW_ACCEL_PREF, "checkbox should show the invert of the default value");
 
   let contentProcessCount = doc.querySelector("#contentProcessCount");
+  is(contentProcessCount.disabled, false, "process count control should be enabled");
   is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default pref value should be default value");
   is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
 
+  let contentProcessCountEnabledDescription = doc.querySelector("#contentProcessCountEnabledDescription");
+  is(contentProcessCountEnabledDescription.hidden, false, "process count enabled description should be shown");
+
+  let contentProcessCountDisabledDescription = doc.querySelector("#contentProcessCountDisabledDescription");
+  is(contentProcessCountDisabledDescription.hidden, true, "process count enabled description should be hidden");
+
   allowHWAccel.click();
   allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, !DEFAULT_HW_ACCEL_PREF,
     "pref value should be opposite of the default value after clicking on checkbox");
   is(allowHWAccel.checked, !allowHWAccelPref, "checkbox should show the invert of the current value");
 
   contentProcessCount.value = 7;
   contentProcessCount.doCommand();
copy from browser/components/preferences/in-content-new/tests/browser_performance.js
copy to browser/components/preferences/in-content-new/tests/browser_performance_non_e10s.js
--- a/browser/components/preferences/in-content-new/tests/browser_performance.js
+++ b/browser/components/preferences/in-content-new/tests/browser_performance_non_e10s.js
@@ -31,109 +31,60 @@ add_task(async function() {
 
   let allowHWAccel = doc.querySelector("#allowHWAccel");
   let allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, DEFAULT_HW_ACCEL_PREF,
     "pref value should be the default value before clicking on checkbox");
   is(allowHWAccel.checked, !DEFAULT_HW_ACCEL_PREF, "checkbox should show the invert of the default value");
 
   let contentProcessCount = doc.querySelector("#contentProcessCount");
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default pref value should be default value");
-  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
+  is(contentProcessCount.disabled, true, "process count control should be disabled");
+
+  let contentProcessCountEnabledDescription = doc.querySelector("#contentProcessCountEnabledDescription");
+  is(contentProcessCountEnabledDescription.hidden, true, "process count enabled description should be hidden");
+
+  let contentProcessCountDisabledDescription = doc.querySelector("#contentProcessCountDisabledDescription");
+  is(contentProcessCountDisabledDescription.hidden, false, "process count enabled description should be shown");
 
   allowHWAccel.click();
   allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, !DEFAULT_HW_ACCEL_PREF,
     "pref value should be opposite of the default value after clicking on checkbox");
   is(allowHWAccel.checked, !allowHWAccelPref, "checkbox should show the invert of the current value");
 
-  contentProcessCount.value = 7;
-  contentProcessCount.doCommand();
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), 7, "pref value should be 7");
-  is(contentProcessCount.selectedItem.value, 7, "selected item should be 7");
-
   allowHWAccel.click();
   allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, DEFAULT_HW_ACCEL_PREF,
     "pref value should be the default value after clicking on checkbox");
   is(allowHWAccel.checked, !allowHWAccelPref, "checkbox should show the invert of the current value");
 
-  contentProcessCount.value = DEFAULT_PROCESS_COUNT;
-  contentProcessCount.doCommand();
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "pref value should be default value");
-  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be default one");
-
   is(performanceSettings.hidden, false, "performance settings section should be still shown");
 
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 add_task(async function() {
   let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   is(prefs.selectedPane, "paneGeneral", "General pane was selected");
 
   let doc = gBrowser.contentDocument;
-  let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
-  let allowHWAccel = doc.querySelector("#allowHWAccel");
-  let contentProcessCount = doc.querySelector("#contentProcessCount");
-  let performanceSettings = doc.querySelector("#performanceSettings");
-
-  useRecommendedPerformanceSettings.click();
-  allowHWAccel.click();
-  contentProcessCount.value = 7;
-  contentProcessCount.doCommand();
-  useRecommendedPerformanceSettings.click();
-
-  is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
-    "pref value should be true before clicking on checkbox");
-  ok(useRecommendedPerformanceSettings.checked, "checkbox should be checked before clicking on checkbox");
-  is(performanceSettings.hidden, true, "performance settings section should be still shown");
-
-  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-add_task(async function() {
-  let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
-
-  let doc = gBrowser.contentDocument;
   let performanceSettings = doc.querySelector("#performanceSettings");
 
   is(performanceSettings.hidden, true, "performance settings section should not be shown");
 
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
 
   is(performanceSettings.hidden, false, "performance settings section should be shown");
 
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 add_task(async function() {
-  Services.prefs.setIntPref("dom.ipc.processCount", 7);
-
-  let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
-
-  let doc = gBrowser.contentDocument;
-
-  let performanceSettings = doc.querySelector("#performanceSettings");
-  is(performanceSettings.hidden, false, "performance settings section should be shown");
-
-  let contentProcessCount = doc.querySelector("#contentProcessCount");
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), 7, "pref value should be 7");
-  is(contentProcessCount.selectedItem.value, 7, "selected item should be 7");
-
-  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-add_task(async function() {
   Services.prefs.setBoolPref("layers.acceleration.disabled", true);
 
   let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   is(prefs.selectedPane, "paneGeneral", "General pane was selected");
 
   let doc = gBrowser.contentDocument;
 
   let performanceSettings = doc.querySelector("#performanceSettings");
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -138,16 +138,31 @@ var gMainPane = {
     }
 
     // Notify observers that the UI is now ready
     Components.classes["@mozilla.org/observer-service;1"]
               .getService(Components.interfaces.nsIObserverService)
               .notifyObservers(window, "main-pane-loaded");
   },
 
+  isE10SEnabled() {
+    let e10sEnabled;
+    try {
+      let e10sStatus = Components.classes["@mozilla.org/supports-PRUint64;1"]
+                         .createInstance(Ci.nsISupportsPRUint64);
+      let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver);
+      appinfo.observe(e10sStatus, "getE10SBlocked", "");
+      e10sEnabled = e10sStatus.data < 2;
+    } catch (e) {
+      e10sEnabled = false;
+    }
+
+    return e10sEnabled;
+  },
+
   enableE10SChange() {
     if (AppConstants.E10S_TESTING_ONLY) {
       let e10sCheckbox = document.getElementById("e10sAutoStart");
       let e10sPref = document.getElementById("browser.tabs.remote.autostart");
       let e10sTempPref = document.getElementById("e10sTempPref");
 
       let prefsToChange;
       if (e10sCheckbox.checked) {
@@ -421,24 +436,36 @@ var gMainPane = {
       accelerationPref.value = accelerationPref.defaultValue;
       performanceSettings.hidden = true;
     } else {
       performanceSettings.hidden = false;
     }
   },
 
   buildContentProcessCountMenuList() {
-    let processCountPref = document.getElementById("dom.ipc.processCount");
-    let bundlePreferences = document.getElementById("bundlePreferences");
-    let label = bundlePreferences.getFormattedString("defaultContentProcessCount",
-      [processCountPref.defaultValue]);
-    let contentProcessCount =
-      document.querySelector(`#contentProcessCount > menupopup >
-                              menuitem[value="${processCountPref.defaultValue}"]`);
-    contentProcessCount.label = label;
+    if (gMainPane.isE10SEnabled()) {
+      let processCountPref = document.getElementById("dom.ipc.processCount");
+      let bundlePreferences = document.getElementById("bundlePreferences");
+      let label = bundlePreferences.getFormattedString("defaultContentProcessCount",
+        [processCountPref.defaultValue]);
+      let contentProcessCount =
+        document.querySelector(`#contentProcessCount > menupopup >
+                                menuitem[value="${processCountPref.defaultValue}"]`);
+      contentProcessCount.label = label;
+
+      document.getElementById("limitContentProcess").disabled = false;
+      document.getElementById("contentProcessCount").disabled = false;
+      document.getElementById("contentProcessCountEnabledDescription").hidden = false;
+      document.getElementById("contentProcessCountDisabledDescription").hidden = true;
+    } else {
+      document.getElementById("limitContentProcess").disabled = true;
+      document.getElementById("contentProcessCount").disabled = true;
+      document.getElementById("contentProcessCountEnabledDescription").hidden = true;
+      document.getElementById("contentProcessCountDisabledDescription").hidden = false;
+    }
   },
 
   // DOWNLOADS
 
   /*
    * Preferences:
    *
    * browser.download.useDownloadDir - bool
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -345,11 +345,12 @@
           <menuitem label="3" value="3"/>
           <menuitem label="4" value="4"/>
           <menuitem label="5" value="5"/>
           <menuitem label="6" value="6"/>
           <menuitem label="7" value="7"/>
         </menupopup>
       </menulist>
     </hbox>
-    <description>&limitContentProcessOption.description;</description>
+    <description id="contentProcessCountEnabledDescription">&limitContentProcessOption.description;</description>
+    <description id="contentProcessCountDisabledDescription">&limitContentProcessOption.disabledDescription;<label class="text-link" href="https://wiki.mozilla.org/Electrolysis">&limitContentProcessOption.disabledDescriptionLink;</label></description>
   </vbox>
 </groupbox>
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -26,16 +26,19 @@ skip-if = os != "win" # This test tests 
 [browser_connection_bug388287.js]
 [browser_cookies_exceptions.js]
 [browser_defaultbrowser_alwayscheck.js]
 [browser_healthreport.js]
 skip-if = true || !healthreport # Bug 1185403 for the "true"
 [browser_homepages_filter_aboutpreferences.js]
 [browser_notifications_do_not_disturb.js]
 [browser_performance.js]
+skip-if = !e10s
+[browser_performance_non_e10s.js]
+skip-if = e10s
 [browser_permissions_urlFieldHidden.js]
 [browser_proxy_backup.js]
 [browser_privacypane_1.js]
 [browser_privacypane_3.js]
 [browser_privacypane_4.js]
 [browser_privacypane_5.js]
 [browser_privacypane_8.js]
 [browser_sanitizeOnShutdown_prefLocked.js]
--- a/browser/components/preferences/in-content/tests/browser_performance.js
+++ b/browser/components/preferences/in-content/tests/browser_performance.js
@@ -31,19 +31,26 @@ add_task(function*() {
 
   let allowHWAccel = doc.querySelector("#allowHWAccel");
   let allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, DEFAULT_HW_ACCEL_PREF,
     "pref value should be the default value before clicking on checkbox");
   is(allowHWAccel.checked, !DEFAULT_HW_ACCEL_PREF, "checkbox should show the invert of the default value");
 
   let contentProcessCount = doc.querySelector("#contentProcessCount");
+  is(contentProcessCount.disabled, false, "process count control should be enabled");
   is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default pref value should be default value");
   is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
 
+  let contentProcessCountEnabledDescription = doc.querySelector("#contentProcessCountEnabledDescription");
+  is(contentProcessCountEnabledDescription.hidden, false, "process count enabled description should be shown");
+
+  let contentProcessCountDisabledDescription = doc.querySelector("#contentProcessCountDisabledDescription");
+  is(contentProcessCountDisabledDescription.hidden, true, "process count enabled description should be hidden");
+
   allowHWAccel.click();
   allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, !DEFAULT_HW_ACCEL_PREF,
     "pref value should be opposite of the default value after clicking on checkbox");
   is(allowHWAccel.checked, !allowHWAccelPref, "checkbox should show the invert of the current value");
 
   contentProcessCount.value = 7;
   contentProcessCount.doCommand();
copy from browser/components/preferences/in-content/tests/browser_performance.js
copy to browser/components/preferences/in-content/tests/browser_performance_non_e10s.js
--- a/browser/components/preferences/in-content/tests/browser_performance.js
+++ b/browser/components/preferences/in-content/tests/browser_performance_non_e10s.js
@@ -31,109 +31,60 @@ add_task(function*() {
 
   let allowHWAccel = doc.querySelector("#allowHWAccel");
   let allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, DEFAULT_HW_ACCEL_PREF,
     "pref value should be the default value before clicking on checkbox");
   is(allowHWAccel.checked, !DEFAULT_HW_ACCEL_PREF, "checkbox should show the invert of the default value");
 
   let contentProcessCount = doc.querySelector("#contentProcessCount");
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default pref value should be default value");
-  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
+  is(contentProcessCount.disabled, true, "process count control should be disabled");
+
+  let contentProcessCountEnabledDescription = doc.querySelector("#contentProcessCountEnabledDescription");
+  is(contentProcessCountEnabledDescription.hidden, true, "process count enabled description should be hidden");
+
+  let contentProcessCountDisabledDescription = doc.querySelector("#contentProcessCountDisabledDescription");
+  is(contentProcessCountDisabledDescription.hidden, false, "process count enabled description should be shown");
 
   allowHWAccel.click();
   allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, !DEFAULT_HW_ACCEL_PREF,
     "pref value should be opposite of the default value after clicking on checkbox");
   is(allowHWAccel.checked, !allowHWAccelPref, "checkbox should show the invert of the current value");
 
-  contentProcessCount.value = 7;
-  contentProcessCount.doCommand();
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), 7, "pref value should be 7");
-  is(contentProcessCount.selectedItem.value, 7, "selected item should be 7");
-
   allowHWAccel.click();
   allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
   is(allowHWAccelPref, DEFAULT_HW_ACCEL_PREF,
     "pref value should be the default value after clicking on checkbox");
   is(allowHWAccel.checked, !allowHWAccelPref, "checkbox should show the invert of the current value");
 
-  contentProcessCount.value = DEFAULT_PROCESS_COUNT;
-  contentProcessCount.doCommand();
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "pref value should be default value");
-  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be default one");
-
   is(performanceSettings.hidden, false, "performance settings section should be still shown");
 
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 add_task(function*() {
   let prefs = yield openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
   is(prefs.selectedPane, "paneGeneral", "General pane was selected");
 
   let doc = gBrowser.contentDocument;
-  let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
-  let allowHWAccel = doc.querySelector("#allowHWAccel");
-  let contentProcessCount = doc.querySelector("#contentProcessCount");
-  let performanceSettings = doc.querySelector("#performanceSettings");
-
-  useRecommendedPerformanceSettings.click();
-  allowHWAccel.click();
-  contentProcessCount.value = 7;
-  contentProcessCount.doCommand();
-  useRecommendedPerformanceSettings.click();
-
-  is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
-    "pref value should be true before clicking on checkbox");
-  ok(useRecommendedPerformanceSettings.checked, "checkbox should be checked before clicking on checkbox");
-  is(performanceSettings.hidden, true, "performance settings section should be still shown");
-
-  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
-  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-add_task(function*() {
-  let prefs = yield openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
-  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
-
-  let doc = gBrowser.contentDocument;
   let performanceSettings = doc.querySelector("#performanceSettings");
 
   is(performanceSettings.hidden, true, "performance settings section should not be shown");
 
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
 
   is(performanceSettings.hidden, false, "performance settings section should be shown");
 
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 add_task(function*() {
-  Services.prefs.setIntPref("dom.ipc.processCount", 7);
-
-  let prefs = yield openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
-  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
-
-  let doc = gBrowser.contentDocument;
-
-  let performanceSettings = doc.querySelector("#performanceSettings");
-  is(performanceSettings.hidden, false, "performance settings section should be shown");
-
-  let contentProcessCount = doc.querySelector("#contentProcessCount");
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), 7, "pref value should be 7");
-  is(contentProcessCount.selectedItem.value, 7, "selected item should be 7");
-
-  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
-  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-add_task(function*() {
   Services.prefs.setBoolPref("layers.acceleration.disabled", true);
 
   let prefs = yield openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
   is(prefs.selectedPane, "paneGeneral", "General pane was selected");
 
   let doc = gBrowser.contentDocument;
 
   let performanceSettings = doc.querySelector("#performanceSettings");
--- a/browser/locales/en-US/chrome/browser/preferences-old/advanced.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences-old/advanced.dtd
@@ -132,10 +132,14 @@
 <!ENTITY useRecommendedPerformanceSettings.accesskey
                                          "U">
 <!ENTITY performanceSettingsLearnMore.label
                                          "Learn more">
 <!ENTITY limitContentProcessOption.label "Content process limit">
 <!ENTITY limitContentProcessOption.description
                                          "Additional content processes can improve performance when using multiple tabs, but will also use more memory.">
 <!ENTITY limitContentProcessOption.accesskey   "L">
+<!ENTITY limitContentProcessOption.disabledDescription
+                                         "Modifying the number of content processes is only possible with multiprocess &brandShortName;.">
+<!ENTITY limitContentProcessOption.disabledDescriptionLink
+                                         "Learn how to check if multiprocess is enabled">
 <!ENTITY allowHWAccel.label              "Use hardware acceleration when available">
 <!ENTITY allowHWAccel.accesskey          "r">
--- a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
@@ -145,10 +145,14 @@
 <!ENTITY useRecommendedPerformanceSettings2.accesskey
                                          "U">
 <!ENTITY performanceSettingsLearnMore.label
                                          "Learn more">
 <!ENTITY limitContentProcessOption.label "Content process limit">
 <!ENTITY limitContentProcessOption.description
                                          "Additional content processes can improve performance when using multiple tabs, but will also use more memory.">
 <!ENTITY limitContentProcessOption.accesskey   "L">
+<!ENTITY limitContentProcessOption.disabledDescription
+                                         "Modifying the number of content processes is only possible with multiprocess &brandShortName;.">
+<!ENTITY limitContentProcessOption.disabledDescriptionLink
+                                         "Learn how to check if multiprocess is enabled">
 <!ENTITY allowHWAccel.label              "Use hardware acceleration when available">
 <!ENTITY allowHWAccel.accesskey          "r">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -495,32 +495,49 @@ html|*.addon-webext-perm-list {
   margin-inline-start: 0;
 }
 
 .popup-notification-description[popupid="addon-webext-permissions"] {
   margin: 0;
   padding: 0;
 }
 
+.popup-notification-description[popupid="addon-installed"] {
+  display: none;
+}
+
+.addon-installed-notification-content {
+  margin-top: 0;
+}
+
+#addon-installed-notification-header {
+  /* Align the text more closely with the icon by clearing some top line height. */
+  margin-top: -1px;
+}
+
 .addon-webext-name {
   display: inline;
   font-weight: bold;
   margin: 0;
 }
 
-.addon-addon-icon {
+.addon-addon-icon,
+.addon-toolbar-icon {
   width: 14px;
   height: 14px;
+  vertical-align: bottom;
+  margin-bottom: 1px;
+}
+
+.addon-addon-icon {
   list-style-image: url("chrome://browser/skin/menuPanel.svg");
   -moz-image-region: rect(0px, 288px, 32px, 256px);
 }
 
 .addon-toolbar-icon {
-  width: 14px;
-  height: 14px;
   list-style-image: url("chrome://browser/skin/menu.svg");
   -moz-context-properties: fill;
   fill: var(--toolbarbutton-icon-fill);
 }
 
 /* Notification icon box */
 
 .notification-anchor-icon:-moz-focusring {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1986,32 +1986,49 @@ html|*.addon-webext-perm-list {
   margin-inline-start: 0;
 }
 
 .popup-notification-description[popupid="addon-webext-permissions"] {
   margin: 0;
   padding: 0;
 }
 
+.popup-notification-description[popupid="addon-installed"] {
+  display: none;
+}
+
+.addon-installed-notification-content {
+  margin-top: 0;
+}
+
+#addon-installed-notification-header {
+  /* Align the text more closely with the icon by clearing some top line height. */
+  margin-top: -1px;
+}
+
 .addon-webext-name {
   display: inline;
   font-weight: bold;
   margin: 0;
 }
 
-.addon-addon-icon {
+.addon-addon-icon,
+.addon-toolbar-icon {
   width: 14px;
   height: 14px;
+  vertical-align: bottom;
+  margin-bottom: 1px;
+}
+
+.addon-addon-icon {
   list-style-image: url("chrome://browser/skin/menuPanel.svg");
   -moz-image-region: rect(0px, 288px, 32px, 256px);
 }
 
 .addon-toolbar-icon {
-  width: 14px;
-  height: 14px;
   list-style-image: url("chrome://browser/skin/menu.svg");
   -moz-context-properties: fill;
   fill: var(--toolbarbutton-icon-fill);
 }
 
 /* Status panel */
 
 .statuspanel-label {
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -164,21 +164,25 @@
 .identity-popup-permission-label,
 .identity-popup-permission-state-label,
 #identity-popup-security-content > description,
 #identity-popup-security-descriptions > description,
 #identity-popup-securityView-header > description,
 #identity-popup-securityView-body > description,
 #identity-popup-permissions-content > description,
 #tracking-protection-content > description {
-  white-space: pre-wrap;
   font-size: 110%;
   margin: 0;
 }
 
+/* This element needs the pre-wrap because we add newlines to it in the code. */
+#identity-popup-content-supplemental {
+  white-space: pre-wrap;
+}
+
 .identity-popup-headline {
   margin: 3px 0 4px;
   font-size: 150%;
 }
 
 .identity-popup-host {
   word-wrap: break-word;
   /* 1em + 2em + 24px is #identity-popup-security-content padding
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1593,32 +1593,49 @@ html|*.addon-webext-perm-list {
   margin-inline-start: 0;
 }
 
 .popup-notification-description[popupid="addon-webext-permissions"] {
   margin: 0;
   padding: 0;
 }
 
+.popup-notification-description[popupid="addon-installed"] {
+  display: none;
+}
+
+.addon-installed-notification-content {
+  margin-top: 0;
+}
+
+#addon-installed-notification-header {
+  /* Align the text more closely with the icon by clearing some top line height. */
+  margin-top: -1px;
+}
+
 .addon-webext-name {
   display: inline;
   font-weight: bold;
   margin: 0;
 }
 
-.addon-addon-icon {
+.addon-addon-icon,
+.addon-toolbar-icon {
   width: 14px;
   height: 14px;
+  vertical-align: bottom;
+  margin-bottom: 1px;
+}
+
+.addon-addon-icon {
   list-style-image: url("chrome://browser/skin/menuPanel.svg");
   -moz-image-region: rect(0px, 288px, 32px, 256px);
 }
 
 .addon-toolbar-icon {
-  width: 14px;
-  height: 14px;
   list-style-image: url("chrome://browser/skin/menu.svg");
   -moz-context-properties: fill;
   fill: var(--toolbarbutton-icon-fill);
 }
 
 /* Notification icon box */
 
 .notification-anchor-icon:-moz-focusring {
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -45,17 +45,17 @@ load 371466-1.xhtml
 load 372554-1.html
 load 375399-1.html
 load 377360-1.xhtml
 load 377960-1.html
 load 377960-2.html
 load 384663-1.html
 load 386000-1.html
 load 386794-1.html
-skip-if(stylo) load 387460-1.html # bug 1323647
+load 387460-1.html
 load 395469-1.xhtml
 load 395469-2.xhtml
 load 398088-1.xul
 skip load 399712-1.html # sporadically times out (bug 473680)
 load 400763-1.html
 load 401993-1.html
 load 404869-1.xul
 load 407818.html
--- a/gfx/thebes/gfxQuartzNativeDrawing.cpp
+++ b/gfx/thebes/gfxQuartzNativeDrawing.cpp
@@ -45,16 +45,22 @@ gfxQuartzNativeDrawing::BeginNativeDrawi
 
     transform.PostTranslate(-mNativeRect.x, -mNativeRect.y);
     mTempDrawTarget->SetTransform(transform);
 
     dt = mTempDrawTarget;
   } else {
     // Clip the DT in case BorrowedCGContext needs to create a new layer.
     // This prevents it from creating a new layer the size of the window.
+    // But make sure that this clip is device pixel aligned.
+    Matrix transform = dt->GetTransform();
+
+    Rect deviceRect = transform.TransformBounds(mNativeRect);
+    deviceRect.RoundOut();
+    mNativeRect = transform.Inverse().TransformBounds(deviceRect);
     mDrawTarget->PushClipRect(mNativeRect);
   }
 
   MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA);
   mCGContext = mBorrowedContext.Init(dt);
 
   if (NS_WARN_IF(!mCGContext)) {
     // Failed borrowing CG context, so we need to clean up.
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -736,27 +736,19 @@ ElementForStyleContext(nsIContent* aPare
     while (!f->IsNumberControlFrame()) {
       f = f->GetParent();
       MOZ_ASSERT(f);
     }
     return f->GetContent()->AsElement();
   }
 
   Element* frameElement = aFrame->GetContent()->AsElement();
-  if (frameElement->IsNativeAnonymous() &&
-      nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(aPseudoType)) {
+  if (frameElement->IsNativeAnonymous()) {
     // NAC-implemented pseudos use the closest non-NAC element as their
     // element to inherit from.
-    //
-    // FIXME(heycam): In theory we shouldn't need to limit this only to
-    // JS-created pseudo-implementing NAC, as all pseudo-implementing
-    // should use the closest non-native anonymous ancestor element as
-    // its originating element.  But removing that part of the condition
-    // reveals some bugs in style resultion with display:contents and
-    // XBL.  See bug 1345809.
     Element* originatingElement =
       nsContentUtils::GetClosestNonNativeAnonymousAncestor(frameElement);
     if (originatingElement) {
       return originatingElement;
     }
   }
 
   if (aParentContent) {
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1182,27 +1182,31 @@ nsPresContext::CompatibilityModeChanged(
     return;
   }
 
   nsIDocument* doc = mShell->GetDocument();
   if (!doc) {
     return;
   }
 
+  StyleSetHandle styleSet = mShell->StyleSet();
+  if (styleSet->IsServo()) {
+    styleSet->AsServo()->CompatibilityModeChanged();
+  }
+
   if (doc->IsSVGDocument()) {
     // SVG documents never load quirk.css.
     return;
   }
 
   bool needsQuirkSheet = CompatibilityMode() == eCompatibility_NavQuirks;
   if (mQuirkSheetAdded == needsQuirkSheet) {
     return;
   }
 
-  StyleSetHandle styleSet = mShell->StyleSet();
   auto cache = nsLayoutStylesheetCache::For(styleSet->BackendType());
   StyleSheet* sheet = cache->QuirkSheet();
 
   if (needsQuirkSheet) {
     // quirk.css needs to come after html.css; we just keep it at the end.
     DebugOnly<nsresult> rv =
       styleSet->AppendStyleSheet(SheetType::Agent, sheet);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "failed to insert quirk.css");
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -10,19 +10,19 @@ load 255982-4.html
 load 264937-1.html
 load 265867-1.html
 load 265867-2.html
 load 286491.html
 load 289864-1.html
 asserts(0-1) load 295292-1.html # Bug 1315855
 load 295292-2.html
 load 302260-1.html
-load 307979-1.html # bug 1338007
-load 309322-1.html # bug 1338007
-load 309322-2.html # bug 1338007
+load 307979-1.html
+load 309322-1.html
+load 309322-2.html
 load 309322-3.html
 load 309322-4.html
 load 310556-1.xhtml
 load 321224.xul
 load 322780-1.xul
 load 323381-1.html
 load 323381-2.html
 asserts-if(gtkWidget,1) asserts-if(Android&&asyncPan,1) asserts-if(stylo,1) load 323386-1.html # Bug 718883
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1368113-1-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<style>
+  input::placeholder { color: green; }
+</style>
+<input type="number" placeholder="This should be green">
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1368113-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+  input::placeholder { color: red; }
+  input.green::placeholder { color: green; }
+</style>
+<input type="number" placeholder="This should be green">
+<script>
+  var i = document.querySelector("input");
+  var s = getComputedStyle(i, "::placeholder");
+  // Make sure we've computed the old color.
+  var oldColor = s.color;
+  i.className = "green";
+</script>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -2003,11 +2003,12 @@ fails-if(styloVsGecko) == 1322512-1.html
 == 1364280-2b.html 1364280-2-ref.html
 == 1364280-2c.html 1364280-2-ref.html
 == 1364335.html 1364335-ref.html
 == 1364360-1.html 1364360-1-ref.html
 == 1365159-1.html 1365159-1-ref.html
 fails-if(!stylo||styloVsGecko) == 1365162-1.html 1365162-1-ref.html
 == 1366144.html 1366144-ref.html
 == 1367592-1.html 1367592-1-ref.html
+== 1368113-1.html 1368113-1-ref.html
 == 1369584-1a.html 1369584-1-ref.html
 == 1369584-1b.html 1369584-1-ref.html
 == 1369954-1.xhtml 1369954-1-ref.xhtml
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-display/display-contents-xbl-6-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<body>
+<div style="color:green">a <b style="color:blue">I</b> c</div>
+<div style="color:blue"><b>L1</b><span style="color:green">2</span></div>
+<div style="color:blue"><b>M1</b><span style="color:green">2</span></div>
+<span style="color:blue"><b>Q1</b><span style="color:green">2</span></span>
+<span style="color:blue"><b>R1</b><span style="color:green">2</span></span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-display/display-contents-xbl-6.xhtml
@@ -0,0 +1,70 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<head>
+  <title>CSS Test: CSS display:contents in XBL</title>
+  <link rel="author" title="William Chen" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1040291"/>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=907396"/>
+  <link rel="help" href="http://dev.w3.org/csswg/css-display"/>
+
+<style>
+.c { display:contents; }
+</style>
+<bindings xmlns="http://www.mozilla.org/xbl"
+          xmlns:xhtml="http://www.w3.org/1999/xhtml">
+  <binding id="a">
+    <content>
+      <style xmlns="http://www.w3.org/1999/xhtml">
+        .a {
+          display: contents;
+          color: blue;
+        }
+      </style>
+      <xhtml:span>a</xhtml:span>
+      <xhtml:span class="a">
+        <children></children>
+      </xhtml:span>
+      <xhtml:span>c</xhtml:span>
+    </content>
+  </binding>
+
+  <binding id="g">
+    <content>
+      <style xmlns="http://www.w3.org/1999/xhtml">
+        .a {
+          display: contents;
+          color: blue;
+        }
+      </style>
+      <xhtml:span class="a"><children includes="b"></children></xhtml:span>
+      <xhtml:span class="a" style="color:green"><children includes="c"></children></xhtml:span>
+    </content>
+  </binding>
+</bindings>
+</head>
+<body>
+<div id="hostI" style="color:green"><b style="display:contents">I</b></div>
+<div id="hostL" style="color:red"><c>2</c><b style="display:contents">L1</b></div>
+<div id="hostM" style="-moz-binding:url(#g); color:red"><c>2</c><b style="display:contents">M1</b></div>
+<div id="hostQ" class="c" style="color:red"><c>2</c><b style="display:contents">Q1</b></div>
+<div id="hostR" class="c" style="-moz-binding:url(#g); color:red"><c>2</c><b style="display:contents">R1</b></div>
+
+<script>
+
+function tweak() {
+  document.body.offsetHeight;
+
+  hostI.style.MozBinding='url(#a)';
+  hostL.style.MozBinding='url(#g)';
+  hostQ.style.MozBinding='url(#g)';
+
+  document.body.offsetHeight;
+  document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("MozReftestInvalidate", tweak);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-display/display-contents-xbl-7-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<body>
+<div style="color:blue">a <span>b</span> c</div>
+<div style="color:blue">a <b>K</b> c</div>
+<span style="color:blue">a <b>P</b> c</span>
+<span style="color:blue">a <span>b</span> c</span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-display/display-contents-xbl-7.xhtml
@@ -0,0 +1,71 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<head>
+  <title>CSS Test: CSS display:contents in XBL</title>
+  <link rel="author" title="William Chen" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1040291"/>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=907396"/>
+  <link rel="help" href="http://dev.w3.org/csswg/css-display"/>
+
+<style>
+.c { display:contents; }
+</style>
+<bindings xmlns="http://www.mozilla.org/xbl"
+          xmlns:xhtml="http://www.w3.org/1999/xhtml">
+  <binding id="b">
+    <content>
+      <style xmlns="http://www.w3.org/1999/xhtml">
+        .b {
+          display: contents;
+          color: blue;
+        }
+	.b::after {
+          content: 'c';
+        }
+      </style>
+      <xhtml:span class="b">
+        a <children></children>
+      </xhtml:span>
+    </content>
+  </binding>
+</bindings>
+</head>
+<body>
+<div id="host2" style="-moz-binding: url(#b);"></div>
+<div id="hostK" style="-moz-binding:url(#b); color:red"><b>K</b></div>
+<div id="hostP" class="c" style="-moz-binding:url(#b); color:red"><b>P</b></div>
+<div id="hostT" style="-moz-binding: url(#b);"></div>
+
+<script>
+
+function tweak() {
+  document.body.offsetHeight;
+
+  function span(s) {
+    var elm = document.createElement("span");
+    elm.textContent = s;
+    return elm;
+  }
+
+  var elm = span("b");
+  elm.style.display = "contents";
+  elm.style.color = "blue";
+  host2.appendChild(elm);
+
+  hostT.className="c";
+
+  var elm = span("b");
+  elm.style.display = "contents";
+  elm.style.color = "blue";
+  hostT.appendChild(elm);
+
+  document.body.offsetHeight;
+  document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("MozReftestInvalidate", tweak);
+</script>
+</body>
+</html>
--- a/layout/reftests/css-display/display-contents-xbl-ref.html
+++ b/layout/reftests/css-display/display-contents-xbl-ref.html
@@ -1,17 +1,16 @@
 <!DOCTYPE html>
 <!--
      Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html>
 <body>
 <div>a <span style="color:blue">b</span> c</div>
-<div style="color:blue">a <span>b</span> c</div>
 <div>a <span style="color:blue">b</span> cd</div>
 <div>a <span style="color:blue">b</span> c</div>
 <div>a <span style="color:blue">b</span> c</div>
 <div><b>One</b><i>Two</i></div>
 <div><b>One</b><i>Two</i></div>
 <div><b>One</b><i>Two</i></div>
 <div><b>Oneb</b><i>Two</i></div>
 <div><b>Oneb</b><i>Two</i></div>
@@ -20,27 +19,19 @@
 <div><b>One</b><i>Twoi</i></div>
 <div><b>One</b><i>Twoi</i></div>
 <div><b>bOne</b><i>Two</i></div>
 <div><b>bOne</b><i>Two</i></div>
 <div><b>bOne</b><i>Two</i></div>
 <div><b>One</b><i>iTwo</i></div>
 <div><b>One</b><i>iTwo</i></div>
 <div><b>One</b><i>iTwo</i></div>
-<div style="color:green">a <b style="color:blue">I</b> c</div>
 <div style="color:green">a <b style="color:blue">J</b> c</div>
-<div style="color:blue">a <b>K</b> c</div>
-<div style="color:blue"><b>L1</b><span style="color:green">2</span></div>
-<div style="color:blue"><b>M1</b><span style="color:green">2</span></div>
 <span style="color:green">a <b style="color:blue">O</b> c</span>
-<span style="color:blue">a <b>P</b> c</span>
-<span style="color:blue"><b>Q1</b><span style="color:green">2</span></span>
-<span style="color:blue"><b>R1</b><span style="color:green">2</span></span>
 <span>a <span style="color:blue">b</span> c</span>
-<span style="color:blue">a <span>b</span> c</span>
 <span>a <span style="color:blue">b</span> cd</span>
 <span>a <span style="color:blue">b</span> c</span>
 <span>a <span style="color:blue">b</span> c</span>
 <span><b>One</b><i>Two</i></span>
 <span><b>One</b><i>Two</i></span>
 <span><b>One</b><i>Two</i></span>
 <span><b>Oneb</b><i>Two</i></span>
 <span><b>Oneb</b><i>Two</i></span>
--- a/layout/reftests/css-display/display-contents-xbl.xhtml
+++ b/layout/reftests/css-display/display-contents-xbl.xhtml
@@ -25,33 +25,16 @@
       <xhtml:span>a</xhtml:span>
       <xhtml:span class="a">
         <children></children>
       </xhtml:span>
       <xhtml:span>c</xhtml:span>
     </content>
   </binding>
 
-  <binding id="b">
-    <content>
-      <style xmlns="http://www.w3.org/1999/xhtml">
-        .b {
-          display: contents;
-          color: blue;
-        }
-	.b::after {
-          content: 'c';
-        }
-      </style>
-      <xhtml:span class="b">
-        a <children></children>
-      </xhtml:span>
-    </content>
-  </binding>
-
   <binding id="c">
     <content>
       <xhtml:span>a</xhtml:span>
       <xhtml:span style="color:blue"> <children></children> </xhtml:span>
       <xhtml:span>c</xhtml:span>
     </content>
   </binding>
 
@@ -106,17 +89,16 @@
       <xhtml:span class="a"><children includes="b"></children></xhtml:span>
       <xhtml:span class="a" style="color:green"><children includes="c"></children></xhtml:span>
     </content>
   </binding>
 </bindings>
 </head>
 <body>
 <div id="host1" style="-moz-binding: url(#a);"></div>
-<div id="host2" style="-moz-binding: url(#b);"></div>
 <div id="host3" style="-moz-binding: url(#c); display: contents;"></div>d
 <div id="host4" style="-moz-binding: url(#d);"></div>
 <div id="host5" style="-moz-binding: url(#e);"></div>
 <div            style="-moz-binding: url(#f)"><i>Two</i><b>One</b></div>
 <div            style="-moz-binding: url(#f)"><i style="display: contents;">Two</i><b>One</b></div>
 <div            style="-moz-binding: url(#f)"><i>Two</i><b style="display: contents;">One</b></div>
 <div id="host6" style="-moz-binding: url(#f)"><i>Two</i><b>One</b></div>
 <div id="host7" style="-moz-binding: url(#f)"><i style="display: contents;">Two</i><b>One</b></div>
@@ -125,27 +107,19 @@
 <div id="hostA" style="-moz-binding: url(#f)"><i style="display: contents;">Two</i><b>One</b></div>
 <div id="hostB" style="-moz-binding: url(#f)"><i>Two</i><b style="display: contents;">One</b></div>
 <div id="hostC" style="-moz-binding: url(#f)"><i>Two</i><b>One</b></div>
 <div id="hostD" style="-moz-binding: url(#f)"><i style="display: contents;">Two</i><b>One</b></div>
 <div id="hostE" style="-moz-binding: url(#f)"><i>Two</i><b style="display: contents;">One</b></div>
 <div id="hostF" style="-moz-binding: url(#f)"><i>Two</i><b>One</b></div>
 <div id="hostG" style="-moz-binding: url(#f)"><i style="display: contents;">Two</i><b>One</b></div>
 <div id="hostH" style="-moz-binding: url(#f)"><i>Two</i><b style="display: contents;">One</b></div>
-<div id="hostI" style="color:green"><b style="display:contents">I</b></div>
 <div id="hostJ" style="-moz-binding:url(#a); color:green"><b>J</b></div>
-<div id="hostK" style="-moz-binding:url(#b); color:red"><b>K</b></div>
-<div id="hostL" style="color:red"><c>2</c><b style="display:contents">L1</b></div>
-<div id="hostM" style="-moz-binding:url(#g); color:red"><c>2</c><b style="display:contents">M1</b></div>
 <div id="hostO" class="c" style="-moz-binding:url(#a); color:green"><b>O</b></div>
-<div id="hostP" class="c" style="-moz-binding:url(#b); color:red"><b>P</b></div>
-<div id="hostQ" class="c" style="color:red"><c>2</c><b style="display:contents">Q1</b></div>
-<div id="hostR" class="c" style="-moz-binding:url(#g); color:red"><c>2</c><b style="display:contents">R1</b></div>
 <div id="hostS" style="-moz-binding: url(#a);"></div>
-<div id="hostT" style="-moz-binding: url(#b);"></div>
 <div id="hostU" style="-moz-binding: url(#c); display: contents;"></div>d
 <div id="hostV" style="-moz-binding: url(#d);"></div>
 <div id="hostW" style="-moz-binding: url(#e);"></div>
 <div class="c"  style="-moz-binding: url(#f)"><i>Two</i><b>One</b></div>
 <div class="c"  style="-moz-binding: url(#f)"><i style="display: contents;">Two</i><b>One</b></div>
 <div class="c"  style="-moz-binding: url(#f)"><i>Two</i><b style="display: contents;">One</b></div>
 <div id="hostX" style="-moz-binding: url(#f)"><i>Two</i><b>One</b></div>
 <div id="hostY" style="-moz-binding: url(#f)"><i style="display: contents;">Two</i><b>One</b></div>
@@ -175,21 +149,16 @@ function tweak() {
     var elm = document.createElement(tag);
     elm.textContent = tag;
     return elm;
   }
 
   // Span should be distributed to the xbl:children insertion point between 'a' and 'c'.
   host1.appendChild(span("b"));
 
-  var elm = span("b");
-  elm.style.display = "contents";
-  elm.style.color = "blue";
-  host2.appendChild(elm);
-
   host3.appendChild(span("b"));
   host4.appendChild(span("b"));
   host5.appendChild(span("b"));
 
   host6.appendChild(elem("b"));
   host7.appendChild(elem("b"));
   host8.appendChild(elem("b"));
 
@@ -201,23 +170,19 @@ function tweak() {
   hostD.insertBefore(elem("b"), hostD.firstChild);
   hostE.insertBefore(elem("b"), hostE.firstChild);
 
   hostF.insertBefore(elem("i"), hostF.firstChild);
   hostG.insertBefore(elem("i"), hostG.firstChild);
   hostH.insertBefore(elem("i"), hostH.firstChild);
 
   document.body.offsetHeight;
-  hostI.style.MozBinding='url(#a)';
-  hostL.style.MozBinding='url(#g)';
   hostO.style.MozBinding='url(#a)';
-  hostQ.style.MozBinding='url(#g)';
 
   hostS.className="c";
-  hostT.className="c";
   hostU.className="c";
   hostV.className="c";
   hostW.className="c";
   hostX.className="c";
   hostY.className="c";
   hostZ.className="c";
   hosta.className="c";
   hostb.className="c";
@@ -226,21 +191,16 @@ function tweak() {
   hoste.className="c";
   hostf.className="c";
   hostg.className="c";
   hosth.className="c";
   hosti.className="c";
 
   hostS.appendChild(span("b"));
 
-  var elm = span("b");
-  elm.style.display = "contents";
-  elm.style.color = "blue";
-  hostT.appendChild(elm);
-
   hostU.appendChild(span("b"));
   hostV.appendChild(span("b"));
   hostW.appendChild(span("b"));
 
   hostX.appendChild(elem("b"));
   hostY.appendChild(elem("b"));
   hostZ.appendChild(elem("b"));
 
--- a/layout/reftests/css-display/reftest.list
+++ b/layout/reftests/css-display/reftest.list
@@ -18,14 +18,16 @@ fuzzy-if(winWidget,12,100) skip-if(stylo
 == display-contents-495385-2d.html display-contents-495385-2d-ref.html
 fuzzy-if(Android,7,3935) == display-contents-xbl.xhtml display-contents-xbl-ref.html
 fuzzy-if(Android,7,1186) pref(dom.webcomponents.enabled,true) skip-if(styloVsGecko||stylo) == display-contents-shadow-dom-1.html display-contents-shadow-dom-1-ref.html
 == display-contents-xbl-2.xul display-contents-xbl-2-ref.xul
 asserts(1) asserts-if(styloVsGecko,2) == display-contents-xbl-3.xul display-contents-xbl-3-ref.xul # bug 1089223
 skip == display-contents-xbl-4.xul display-contents-xbl-4-ref.xul # fails (not just asserts) due to bug 1089223
 asserts(0-1) fuzzy-if(Android,8,3216) == display-contents-fieldset.html display-contents-fieldset-ref.html # bug 1089223
 asserts(1) asserts-if(styloVsGecko,2) == display-contents-xbl-5.xul display-contents-xbl-3-ref.xul # bug 1089223
+fails-if(!stylo) == display-contents-xbl-6.xhtml display-contents-xbl-6-ref.html # bug 1345809
+== display-contents-xbl-7.xhtml display-contents-xbl-7-ref.html
 == display-contents-list-item-child.html display-contents-list-item-child-ref.html
 == display-contents-dyn-insert-text.html display-contents-dyn-insert-text-ref.html
 == display-contents-writing-mode-1.html display-contents-writing-mode-1-ref.html
 == display-contents-writing-mode-2.html display-contents-writing-mode-2-ref.html
 needs-focus == display-contents-state-change.html display-contents-state-change-ref.html
-== display-flow-root-001.html display-flow-root-001-ref.html
\ No newline at end of file
+== display-flow-root-001.html display-flow-root-001-ref.html
--- a/layout/reftests/css-ui-invalid/default-style/reftest.list
+++ b/layout/reftests/css-ui-invalid/default-style/reftest.list
@@ -1,10 +1,10 @@
 == input.html input-ref.html
-fuzzy-if(webrender,1-1,6-6) == button.html button-ref.html
+fuzzy-if(webrender,1-1,6-6) fuzzy-if(cocoaWidget,2,32) == button.html button-ref.html
 == textarea.html textarea-ref.html
 == select.html select-ref.html
 == fieldset.html fieldset-ref.html
 == output.html output-ref.html
 random-if(winWidget) needs-focus == input-focus.html input-focus-ref.html # Intermittent failures, bug 660224
-needs-focus == button-focus.html button-focus-ref.html
+needs-focus fuzzy-if(cocoaWidget,1,10) == button-focus.html button-focus-ref.html
 needs-focus == textarea-focus.html textarea-focus-ref.html
 random-if(winWidget) needs-focus == select-focus.html select-focus-ref.html # Intermittent failures, bug 660224
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -53,16 +53,18 @@ SERVO_BINDING_FUNC(Servo_StyleSheet_Size
 SERVO_BINDING_FUNC(Servo_StyleSet_Init, RawServoStyleSetOwned, RawGeckoPresContextOwned pres_context)
 SERVO_BINDING_FUNC(Servo_StyleSet_Clear, void,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_StyleSet_RebuildData, void,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_StyleSet_MediumFeaturesChanged, bool,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_StyleSet_Drop, void, RawServoStyleSetOwned set)
+SERVO_BINDING_FUNC(Servo_StyleSet_CompatModeChanged, void,
+                   RawServoStyleSetBorrowed raw_data)
 SERVO_BINDING_FUNC(Servo_StyleSet_AppendStyleSheet, void,
                    RawServoStyleSetBorrowed set,
                    RawServoStyleSheetBorrowed sheet,
                    uint64_t unique_id)
 SERVO_BINDING_FUNC(Servo_StyleSet_PrependStyleSheet, void,
                    RawServoStyleSetBorrowed set,
                    RawServoStyleSheetBorrowed sheet,
                    uint64_t unique_id)
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -1118,16 +1118,22 @@ ServoStyleSet::RebuildData()
 void
 ServoStyleSet::ClearDataAndMarkDeviceDirty()
 {
   ClearNonInheritingStyleContexts();
   Servo_StyleSet_Clear(mRawSet.get());
   mStylistState |= StylistState::FullyDirty;
 }
 
+void
+ServoStyleSet::CompatibilityModeChanged()
+{
+  Servo_StyleSet_CompatModeChanged(mRawSet.get());
+}
+
 already_AddRefed<ServoComputedValues>
 ServoStyleSet::ResolveServoStyle(Element* aElement)
 {
   UpdateStylistIfNeeded();
   return Servo_ResolveStyle(aElement, mRawSet.get(),
                             mAllowResolveStaleStyles).Consume();
 }
 
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -342,16 +342,21 @@ public:
   /**
    * Clears the style data, both style sheet data and cached non-inheriting
    * style contexts, and marks the stylist as needing an unconditional full
    * rebuild, including a device reset.
    */
   void ClearDataAndMarkDeviceDirty();
 
   /**
+   * Notifies the Servo stylesheet that the document's compatibility mode has changed.
+   */
+  void CompatibilityModeChanged();
+
+  /**
    * Resolve style for the given element, and return it as a
    * ServoComputedValues, not an nsStyleContext.
    */
   already_AddRefed<ServoComputedValues> ResolveServoStyle(dom::Element* aElement);
 
   bool GetKeyframesForName(const nsString& aName,
                            const nsTimingFunction& aTimingFunction,
                            const ServoComputedValues* aComputedValues,
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -99,17 +99,17 @@ to mochitest command.
   * :-moz-tree bits bug 1348488
     * test_selectors.html `:-moz-tree` [10]
 * Unsupported pseudo-classes
   * :-moz-lwtheme-* bug 1367312
     * test_selectors.html `:-moz-lwtheme` [3]
   * :-moz-window-inactive bug 1348489
     * test_selectors.html `:-moz-window-inactive` [2]
 * Quirks mode support
-  * test_hover_quirk.html: hover quirks bug 1355724 [6]
+  * test_hover_quirk.html: hover quirks bug 1371963 [2]
 * Unit should be preserved after parsing servo/servo#15346
   * test_units_time.html [1]
 * getComputedStyle style doesn't contain custom properties bug 1336891
   * test_variable_serialization_computed.html [35]
   * test_variables.html `custom property name` [2]
 * test_css_supports.html: issues around @supports syntax servo/servo#15482 [2]
 * test_author_specified_style.html: support serializing color as author specified bug 1348165 [27]
 * browser_newtab_share_rule_processors.js: agent style sheet sharing [1]
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -204,16 +204,17 @@ pub struct Document {
     location: MutNullableJS<Location>,
     content_type: DOMString,
     last_modified: Option<String>,
     encoding: Cell<EncodingRef>,
     has_browsing_context: bool,
     is_html_document: bool,
     activity: Cell<DocumentActivity>,
     url: DOMRefCell<ServoUrl>,
+    #[ignore_heap_size_of = "defined in selectors"]
     quirks_mode: Cell<QuirksMode>,
     /// Caches for the getElement methods
     id_map: DOMRefCell<HashMap<Atom, Vec<JS<Element>>>>,
     tag_map: DOMRefCell<HashMap<LocalName, JS<HTMLCollection>>>,
     tagns_map: DOMRefCell<HashMap<QualName, JS<HTMLCollection>>>,
     classes_map: DOMRefCell<HashMap<Vec<Atom>, JS<HTMLCollection>>>,
     images: MutNullableJS<HTMLCollection>,
     embeds: MutNullableJS<HTMLCollection>,
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -81,17 +81,17 @@ use html5ever::serialize::SerializeOpts;
 use html5ever::serialize::TraversalScope;
 use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
 use js::jsapi::{HandleValue, JSAutoCompartment};
 use net_traits::request::CorsSettings;
 use ref_filter_map::ref_filter_map;
 use script_layout_interface::message::ReflowQueryType;
 use script_thread::Runnable;
 use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
 use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
 use selectors::matching::{RelevantLinkStatus, matches_selector_list};
 use servo_atoms::Atom;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::cell::{Cell, Ref};
 use std::convert::TryFrom;
 use std::default::Default;
@@ -2058,17 +2058,19 @@ impl ElementMethods for Element {
         self.upcast::<Node>().remove_self();
     }
 
     // https://dom.spec.whatwg.org/#dom-element-matches
     fn Matches(&self, selectors: DOMString) -> Fallible<bool> {
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
             Err(_) => Err(Error::Syntax),
             Ok(selectors) => {
-                let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
+                let quirks_mode = document_from_node(self).quirks_mode();
+                let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
+                                                   quirks_mode);
                 Ok(matches_selector_list(&selectors, &Root::from_ref(self), &mut ctx))
             }
         }
     }
 
     // https://dom.spec.whatwg.org/#dom-element-webkitmatchesselector
     fn WebkitMatchesSelector(&self, selectors: DOMString) -> Fallible<bool> {
         self.Matches(selectors)
@@ -2077,17 +2079,19 @@ impl ElementMethods for Element {
     // https://dom.spec.whatwg.org/#dom-element-closest
     fn Closest(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
             Err(_) => Err(Error::Syntax),
             Ok(selectors) => {
                 let root = self.upcast::<Node>();
                 for element in root.inclusive_ancestors() {
                     if let Some(element) = Root::downcast::<Element>(element) {
-                        let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
+                        let quirks_mode = document_from_node(self).quirks_mode();
+                        let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
+                                                           quirks_mode);
                         if matches_selector_list(&selectors, &element, &mut ctx) {
                             return Ok(Some(element));
                         }
                     }
                 }
                 Ok(None)
             }
         }
@@ -2427,17 +2431,17 @@ impl<'a> ::selectors::Element for Root<E
     }
 
     fn get_namespace(&self) -> &Namespace {
         self.namespace()
     }
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pseudo_class: &NonTSPseudoClass,
-                                    _: &mut MatchingContext,
+                                    _: &mut LocalMatchingContext<Self::Impl>,
                                     _: &RelevantLinkStatus,
                                     _: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::Link |
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -342,21 +342,21 @@ impl<'a> QuerySelectorIterator {
 }
 
 impl<'a> Iterator for QuerySelectorIterator {
     type Item = Root<Node>;
 
     fn next(&mut self) -> Option<Root<Node>> {
         let selectors = &self.selectors;
 
-        // TODO(cgaebel): Is it worth it to build a bloom filter here
-        // (instead of passing `None`)? Probably.
-        let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
-
         self.iterator.by_ref().filter_map(|node| {
+            // TODO(cgaebel): Is it worth it to build a bloom filter here
+            // (instead of passing `None`)? Probably.
+            let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
+                node.owner_doc().quirks_mode());
             if let Some(element) = Root::downcast(node) {
                 if matches_selector_list(selectors, &element, &mut ctx) {
                     return Some(Root::upcast(element));
                 }
             }
             None
         }).next()
     }
@@ -715,17 +715,18 @@ impl Node {
     // https://dom.spec.whatwg.org/#dom-parentnode-queryselector
     pub fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
         // Step 1.
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
             // Step 2.
             Err(_) => Err(Error::Syntax),
             // Step 3.
             Ok(selectors) => {
-                let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
+                let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
+                                                   self.owner_doc().quirks_mode());
                 Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| {
                     matches_selector_list(&selectors, element, &mut ctx)
                 }))
             }
         }
     }
 
     /// https://dom.spec.whatwg.org/#scope-match-a-selectors-string
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -45,17 +45,18 @@ use gfx_traits::ByteIndex;
 use html5ever::{LocalName, Namespace};
 use msg::constellation_msg::{BrowsingContextId, PipelineId};
 use range::Range;
 use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, TrustedNodeAddress};
 use script_layout_interface::{OpaqueStyleAndLayoutData, StyleData};
 use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode};
 use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
 use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, RelevantLinkStatus, VisitedHandlingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
+use selectors::matching::VisitedHandlingMode;
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::fmt;
 use std::fmt::Debug;
 use std::hash::{Hash, Hasher};
 use std::marker::PhantomData;
 use std::mem::transmute;
 use std::sync::atomic::Ordering;
@@ -716,17 +717,17 @@ impl<'le> ::selectors::Element for Servo
                             _context: &mut MatchingContext)
                             -> bool
     {
         false
     }
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pseudo_class: &NonTSPseudoClass,
-                                    _: &mut MatchingContext,
+                                    _: &mut LocalMatchingContext<Self::Impl>,
                                     _: &RelevantLinkStatus,
                                     _: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::Link |
@@ -1227,17 +1228,17 @@ impl<'le> ::selectors::Element for Servo
                 };
                 values.iter().any(|v| v.eval_selector(operation))
             }
         }
     }
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     _: &NonTSPseudoClass,
-                                    _: &mut MatchingContext,
+                                    _: &mut LocalMatchingContext<Self::Impl>,
                                     _: &RelevantLinkStatus,
                                     _: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         // NB: This could maybe be implemented
         warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
         false
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -1,16 +1,16 @@
 /* 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 attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint};
 use bloom::BloomFilter;
 use parser::{AncestorHashes, Combinator, Component, LocalName};
-use parser::{Selector, SelectorIter, SelectorList};
+use parser::{Selector, SelectorImpl, SelectorIter, SelectorList};
 use std::borrow::Borrow;
 use tree::Element;
 
 // The bloom filter for descendant CSS selectors will have a <1% false
 // positive rate until it has this many selectors in it, then it will
 // rapidly increase.
 pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;
 
@@ -95,16 +95,29 @@ pub enum VisitedHandlingMode {
     /// All links are matched as if they are unvisted.
     AllLinksUnvisited,
     /// A element's "relevant link" is the element being matched if it is a link
     /// or the nearest ancestor link. The relevant link is matched as though it
     /// is visited, and all other links are matched as if they are unvisited.
     RelevantLinkVisited,
 }
 
+/// Which quirks mode is this document in.
+///
+/// See: https://quirks.spec.whatwg.org/
+#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
+pub enum QuirksMode {
+    /// Quirks mode.
+    Quirks,
+    /// Limited quirks mode.
+    LimitedQuirks,
+    /// No quirks mode.
+    NoQuirks,
+}
+
 /// Data associated with the matching process for a element.  This context is
 /// used across many selectors for an element, so it's not appropriate for
 /// transient data that applies to only a single selector.
 #[derive(Clone)]
 pub struct MatchingContext<'a> {
     /// Output that records certains relations between elements noticed during
     /// matching (and also extended after matching).
     pub relations: StyleRelations,
@@ -114,49 +127,141 @@ pub struct MatchingContext<'a> {
     pub bloom_filter: Option<&'a BloomFilter>,
     /// Input that controls how matching for links is handled.
     pub visited_handling: VisitedHandlingMode,
     /// Output that records whether we encountered a "relevant link" while
     /// matching _any_ selector for this element. (This differs from
     /// `RelevantLinkStatus` which tracks the status for the _current_ selector
     /// only.)
     pub relevant_link_found: bool,
+    /// The quirks mode of the document.
+    pub quirks_mode: QuirksMode,
 }
 
 impl<'a> MatchingContext<'a> {
     /// Constructs a new `MatchingContext`.
     pub fn new(matching_mode: MatchingMode,
-               bloom_filter: Option<&'a BloomFilter>)
+               bloom_filter: Option<&'a BloomFilter>,
+               quirks_mode: QuirksMode)
                -> Self
     {
         Self {
             relations: StyleRelations::empty(),
             matching_mode: matching_mode,
             bloom_filter: bloom_filter,
             visited_handling: VisitedHandlingMode::AllLinksUnvisited,
             relevant_link_found: false,
+            quirks_mode: quirks_mode,
         }
     }
 
     /// Constructs a new `MatchingContext` for use in visited matching.
     pub fn new_for_visited(matching_mode: MatchingMode,
                            bloom_filter: Option<&'a BloomFilter>,
-                           visited_handling: VisitedHandlingMode)
+                           visited_handling: VisitedHandlingMode,
+                           quirks_mode: QuirksMode)
                            -> Self
     {
         Self {
             relations: StyleRelations::empty(),
             matching_mode: matching_mode,
             bloom_filter: bloom_filter,
             visited_handling: visited_handling,
             relevant_link_found: false,
+            quirks_mode: quirks_mode,
         }
     }
 }
 
+/// Holds per-element data alongside a pointer to MatchingContext.
+pub struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> {
+    /// Shared `MatchingContext`.
+    pub shared: &'a mut MatchingContext<'b>,
+    /// A reference to the base selector we're matching against.
+    pub selector: &'a Selector<Impl>,
+    /// The offset of the current compound selector being matched, kept up to date by
+    /// the callees when the iterator is advanced. This, in conjunction with the selector
+    /// reference above, allows callees to synthesize an iterator for the current compound
+    /// selector on-demand. This is necessary because the primary iterator may already have
+    /// been advanced partway through the current compound selector, and the callee may need
+    /// the whole thing.
+    offset: usize,
+    /// Holds a bool flag to see if LocalMatchingContext is within a functional
+    /// pseudo class argument. This is used for pseudo classes like
+    /// `:-moz-any` or `:not`. If this flag is true, :active and :hover
+    /// quirk shouldn't match.
+    pub within_functional_pseudo_class_argument: bool,
+}
+
+impl<'a, 'b, Impl> LocalMatchingContext<'a, 'b, Impl>
+    where Impl: SelectorImpl
+{
+    /// Constructs a new `LocalMatchingContext`.
+    pub fn new(shared: &'a mut MatchingContext<'b>,
+               selector: &'a Selector<Impl>) -> Self {
+        Self {
+            shared: shared,
+            selector: selector,
+            offset: 0,
+            within_functional_pseudo_class_argument: false,
+        }
+    }
+
+    /// Updates offset of Selector to show new compound selector.
+    /// To be able to correctly re-synthesize main SelectorIter.
+    pub fn note_next_sequence(&mut self, selector_iter: &SelectorIter<Impl>) {
+        if let QuirksMode::Quirks = self.shared.quirks_mode {
+            self.offset = self.selector.len() - selector_iter.selector_length();
+        }
+    }
+
+    /// Returns true if current compound selector matches :active and :hover quirk.
+    /// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
+    pub fn active_hover_quirk_matches(&mut self) -> bool {
+        if self.shared.quirks_mode != QuirksMode::Quirks ||
+           self.within_functional_pseudo_class_argument {
+            return false;
+        }
+
+        let mut iter = if self.offset == 0 {
+            self.selector.iter()
+        } else {
+            self.selector.iter_from(self.offset)
+        };
+
+        return iter.all(|simple| {
+            match *simple {
+                Component::LocalName(_) |
+                Component::AttributeInNoNamespaceExists { .. } |
+                Component::AttributeInNoNamespace { .. } |
+                Component::AttributeOther(_) |
+                Component::ID(_) |
+                Component::Class(_) |
+                Component::PseudoElement(_) |
+                Component::Negation(_) |
+                Component::FirstChild |
+                Component::LastChild |
+                Component::OnlyChild |
+                Component::Empty |
+                Component::NthChild(_, _) |
+                Component::NthLastChild(_, _) |
+                Component::NthOfType(_, _) |
+                Component::NthLastOfType(_, _) |
+                Component::FirstOfType |
+                Component::LastOfType |
+                Component::OnlyOfType => false,
+                Component::NonTSPseudoClass(ref pseudo_class) => {
+                    Impl::is_active_or_hover(pseudo_class)
+                },
+                _ => true,
+            }
+        });
+    }
+}
+
 pub fn matches_selector_list<E>(selector_list: &SelectorList<E::Impl>,
                                 element: &E,
                                 context: &mut MatchingContext)
                                 -> bool
     where E: Element
 {
     selector_list.0.iter().any(|selector_and_hashes| {
         matches_selector(&selector_and_hashes.selector,
@@ -356,56 +461,59 @@ pub fn matches_selector<E, F>(selector: 
 {
     // Use the bloom filter to fast-reject.
     if let Some(filter) = context.bloom_filter {
         if !may_match::<E>(hashes, filter) {
             return false;
         }
     }
 
-    matches_complex_selector(selector, offset, element, context, flags_setter)
+    let mut local_context = LocalMatchingContext::new(context, selector);
+    matches_complex_selector(&selector, offset, element, &mut local_context, flags_setter)
 }
 
 /// Matches a complex selector.
 pub fn matches_complex_selector<E, F>(complex_selector: &Selector<E::Impl>,
                                       offset: usize,
                                       element: &E,
-                                      context: &mut MatchingContext,
+                                      mut context: &mut LocalMatchingContext<E::Impl>,
                                       flags_setter: &mut F)
                                       -> bool
     where E: Element,
           F: FnMut(&E, ElementSelectorFlags),
 {
     let mut iter = if offset == 0 {
         complex_selector.iter()
     } else {
         complex_selector.iter_from(offset)
     };
 
     if cfg!(debug_assertions) {
-        if context.matching_mode == MatchingMode::ForStatelessPseudoElement {
+        if context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
             assert!(iter.clone().any(|c| {
                 matches!(*c, Component::PseudoElement(..))
             }));
         }
     }
 
-    if context.matching_mode == MatchingMode::ForStatelessPseudoElement {
+    if context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
         match *iter.next().unwrap() {
             // Stateful pseudo, just don't match.
             Component::NonTSPseudoClass(..) => return false,
             Component::PseudoElement(..) => {
                 // Pseudo, just eat the whole sequence.
                 let next = iter.next();
                 debug_assert!(next.is_none(),
                               "Someone messed up pseudo-element parsing?");
 
                 if iter.next_sequence().is_none() {
                     return true;
                 }
+                // Inform the context that the we've advanced to the next compound selector.
+                context.note_next_sequence(&mut iter);
             }
             _ => panic!("Used MatchingMode::ForStatelessPseudoElement in a non-pseudo selector"),
         }
     }
 
     match matches_complex_selector_internal(iter,
                                             element,
                                             context,
@@ -413,33 +521,35 @@ pub fn matches_complex_selector<E, F>(co
                                             flags_setter) {
         SelectorMatchingResult::Matched => true,
         _ => false
     }
 }
 
 fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Impl>,
                                            element: &E,
-                                           context: &mut MatchingContext,
+                                           context: &mut LocalMatchingContext<E::Impl>,
                                            relevant_link: &mut RelevantLinkStatus,
                                            flags_setter: &mut F)
                                            -> SelectorMatchingResult
      where E: Element,
            F: FnMut(&E, ElementSelectorFlags),
 {
-    *relevant_link = relevant_link.examine_potential_link(element, context);
+    *relevant_link = relevant_link.examine_potential_link(element, &mut context.shared);
 
     let matches_all_simple_selectors = selector_iter.all(|simple| {
         matches_simple_selector(simple, element, context, &relevant_link, flags_setter)
     });
 
     debug!("Matching for {:?}, simple selector {:?}, relevant link {:?}",
            element, selector_iter, relevant_link);
 
     let combinator = selector_iter.next_sequence();
+    // Inform the context that the we've advanced to the next compound selector.
+    context.note_next_sequence(&mut selector_iter);
     let siblings = combinator.map_or(false, |c| c.is_sibling());
     if siblings {
         flags_setter(element, HAS_SLOW_SELECTOR_LATER_SIBLINGS);
     }
 
     if !matches_all_simple_selectors {
         return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling;
     }
@@ -512,27 +622,27 @@ fn matches_complex_selector_internal<E, 
     }
 }
 
 /// Determines whether the given element matches the given single selector.
 #[inline]
 fn matches_simple_selector<E, F>(
         selector: &Component<E::Impl>,
         element: &E,
-        context: &mut MatchingContext,
+        context: &mut LocalMatchingContext<E::Impl>,
         relevant_link: &RelevantLinkStatus,
         flags_setter: &mut F)
         -> bool
     where E: Element,
           F: FnMut(&E, ElementSelectorFlags),
 {
     match *selector {
         Component::Combinator(_) => unreachable!(),
         Component::PseudoElement(ref pseudo) => {
-            element.match_pseudo_element(pseudo, context)
+            element.match_pseudo_element(pseudo, context.shared)
         }
         Component::LocalName(LocalName { ref name, ref lower_name }) => {
             let is_html = element.is_html_element_in_html_document();
             element.get_local_name() == select_name(is_html, name, lower_name).borrow()
         }
         Component::ExplicitUniversalType |
         Component::ExplicitAnyNamespace => {
             true
@@ -646,17 +756,24 @@ fn matches_simple_selector<E, F>(
         Component::LastOfType => {
             matches_generic_nth_child(element, 0, 1, true, true, flags_setter)
         }
         Component::OnlyOfType => {
             matches_generic_nth_child(element, 0, 1, true, false, flags_setter) &&
             matches_generic_nth_child(element, 0, 1, true, true, flags_setter)
         }
         Component::Negation(ref negated) => {
-            !negated.iter().all(|ss| matches_simple_selector(ss, element, context, relevant_link, flags_setter))
+            let old_value = context.within_functional_pseudo_class_argument;
+            context.within_functional_pseudo_class_argument = true;
+            let result = !negated.iter().all(|ss| {
+                matches_simple_selector(ss, element, context,
+                                        relevant_link, flags_setter)
+            });
+            context.within_functional_pseudo_class_argument = old_value;
+            result
         }
     }
 }
 
 fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T {
     if is_html {
         local_name_lower
     } else {
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -98,16 +98,20 @@ macro_rules! with_all_bounds {
             type BorrowedLocalName: ?Sized + Eq;
 
             /// non tree-structural pseudo-classes
             /// (see: https://drafts.csswg.org/selectors/#structural-pseudos)
             type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods<Impl = Self>;
 
             /// pseudo-elements
             type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>;
+
+            /// Returns whether the given pseudo class is :active or :hover.
+            #[inline]
+            fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool;
         }
     }
 }
 
 macro_rules! with_bounds {
     ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => {
         with_all_bounds! {
             [$($CommonBounds)* + $($FromStr)* + Display]
@@ -422,17 +426,17 @@ impl<Impl: SelectorImpl> Selector<Impl> 
         SelectorIter {
             iter: self.iter_raw(),
             next_combinator: None,
         }
     }
 
     pub fn iter_from(&self, offset: usize) -> SelectorIter<Impl> {
         // Note: selectors are stored left-to-right but logical order is right-to-left.
-        let iter = self.0.slice[..(self.0.slice.len() - offset)].iter().rev();
+        let iter = self.0.slice[..(self.len() - offset)].iter().rev();
         SelectorIter {
             iter: iter,
             next_combinator: None,
         }
     }
 
     /// Returns an iterator over the entire sequence of simple selectors and combinators,
     /// from right to left.
@@ -446,30 +450,40 @@ impl<Impl: SelectorImpl> Selector<Impl> 
         self.0.slice.iter()
     }
 
     /// Creates a Selector from a vec of Components. Used in tests.
     pub fn from_vec(vec: Vec<Component<Impl>>, specificity_and_flags: u32) -> Self {
         let header = HeaderWithLength::new(SpecificityAndFlags(specificity_and_flags), vec.len());
         Selector(Arc::into_thin(Arc::from_header_and_iter(header, vec.into_iter())))
     }
+
+    /// Returns count of simple selectors and combinators in the Selector.
+    pub fn len(&self) -> usize {
+        self.0.slice.len()
+    }
 }
 
 #[derive(Clone)]
 pub struct SelectorIter<'a, Impl: 'a + SelectorImpl> {
     iter: Rev<slice::Iter<'a, Component<Impl>>>,
     next_combinator: Option<Combinator>,
 }
 
 impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
     /// Prepares this iterator to point to the next sequence to the left,
     /// returning the combinator if the sequence was found.
     pub fn next_sequence(&mut self) -> Option<Combinator> {
         self.next_combinator.take()
     }
+
+    /// Returns remaining count of the simple selectors and combinators in the Selector.
+    pub fn selector_length(&self) -> usize {
+        self.iter.len()
+    }
 }
 
 impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> {
     type Item = &'a Component<Impl>;
     fn next(&mut self) -> Option<Self::Item> {
         debug_assert!(self.next_combinator.is_none(),
                       "You should call next_sequence!");
         match self.iter.next() {
@@ -1706,16 +1720,22 @@ pub mod tests {
         type ClassName = DummyAtom;
         type LocalName = DummyAtom;
         type NamespaceUrl = DummyAtom;
         type NamespacePrefix = DummyAtom;
         type BorrowedLocalName = DummyAtom;
         type BorrowedNamespaceUrl = DummyAtom;
         type NonTSPseudoClass = PseudoClass;
         type PseudoElement = PseudoElement;
+
+        #[inline]
+        fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool {
+            matches!(*pseudo_class, PseudoClass::Active |
+                                    PseudoClass::Hover)
+        }
     }
 
     #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
     pub struct DummyAtom(String);
 
     impl fmt::Display for DummyAtom {
         fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
             <String as fmt::Display>::fmt(&self.0, fmt)
--- a/servo/components/selectors/size_of_tests.rs
+++ b/servo/components/selectors/size_of_tests.rs
@@ -29,16 +29,21 @@ impl SelectorImpl for Impl {
     type ClassName = Atom;
     type LocalName = Atom;
     type NamespaceUrl = Atom;
     type NamespacePrefix = Atom;
     type BorrowedLocalName = Atom;
     type BorrowedNamespaceUrl = Atom;
     type NonTSPseudoClass = PseudoClass;
     type PseudoElement = gecko_like_types::PseudoElement;
+
+    #[inline]
+    fn is_active_or_hover(_pseudo_class: &Self::NonTSPseudoClass) -> bool {
+        unimplemented!()
+    }
 }
 
 impl SelectorMethods for PseudoClass {
     type Impl = Impl;
 
     fn visit<V>(&self, _visitor: &mut V) -> bool
         where V: SelectorVisitor<Impl = Self::Impl> { unimplemented!() }
 }
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 //! Traits that nodes must implement. Breaks the otherwise-cyclic dependency
 //! between layout and style.
 
 use attr::{AttrSelectorOperation, NamespaceConstraint};
-use matching::{ElementSelectorFlags, MatchingContext, RelevantLinkStatus};
+use matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
 use parser::SelectorImpl;
 use std::fmt::Debug;
 
 pub trait Element: Sized + Debug {
     type Impl: SelectorImpl;
 
     fn parent_element(&self) -> Option<Self>;
 
@@ -45,17 +45,17 @@ pub trait Element: Sized + Debug {
     fn attr_matches(&self,
                     ns: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
                     local_name: &<Self::Impl as SelectorImpl>::LocalName,
                     operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>)
                     -> bool;
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
-                                    context: &mut MatchingContext,
+                                    context: &mut LocalMatchingContext<Self::Impl>,
                                     relevant_link: &RelevantLinkStatus,
                                     flags_setter: &mut F) -> bool
         where F: FnMut(&Self, ElementSelectorFlags);
 
     fn match_pseudo_element(&self,
                             pe: &<Self::Impl as SelectorImpl>::PseudoElement,
                             context: &mut MatchingContext)
                             -> bool;
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -28,46 +28,34 @@ use std::ops::Add;
 #[cfg(feature = "servo")] use std::sync::mpsc::Sender;
 use stylearc::Arc;
 use stylist::Stylist;
 use thread_state;
 use time;
 use timer::Timer;
 use traversal::{DomTraversal, TraversalFlags};
 
+pub use selectors::matching::QuirksMode;
+
 /// This structure is used to create a local style context from a shared one.
 #[cfg(feature = "servo")]
 pub struct ThreadLocalStyleContextCreationInfo {
     new_animations_sender: Sender<Animation>,
 }
 
 #[cfg(feature = "servo")]
 impl ThreadLocalStyleContextCreationInfo {
     /// Trivially constructs a `ThreadLocalStyleContextCreationInfo`.
     pub fn new(animations_sender: Sender<Animation>) -> Self {
         ThreadLocalStyleContextCreationInfo {
             new_animations_sender: animations_sender,
         }
     }
 }
 
-/// Which quirks mode is this document in.
-///
-/// See: https://quirks.spec.whatwg.org/
-#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum QuirksMode {
-    /// Quirks mode.
-    Quirks,
-    /// Limited quirks mode.
-    LimitedQuirks,
-    /// No quirks mode.
-    NoQuirks,
-}
-
 /// A global options structure for the style system. We use this instead of
 /// opts to abstract across Gecko and Servo.
 #[derive(Clone)]
 pub struct StyleSystemOptions {
     /// Whether the style sharing cache is disabled.
     pub disable_style_sharing_cache: bool,
     /// Whether we should dump statistics about the style system.
     pub dump_style_statistics: bool,
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -1870,16 +1870,20 @@ extern "C" {
 extern "C" {
     pub fn Servo_StyleSet_RebuildData(set: RawServoStyleSetBorrowed);
 }
 extern "C" {
     pub fn Servo_StyleSet_MediumFeaturesChanged(set: RawServoStyleSetBorrowed)
      -> bool;
 }
 extern "C" {
+    pub fn Servo_StyleSet_CompatModeChanged(raw_data:
+                                                RawServoStyleSetBorrowed);
+}
+extern "C" {
     pub fn Servo_StyleSet_AppendStyleSheet(set: RawServoStyleSetBorrowed,
                                            sheet: RawServoStyleSheetBorrowed,
                                            unique_id: u64);
 }
 extern "C" {
     pub fn Servo_StyleSet_PrependStyleSheet(set: RawServoStyleSetBorrowed,
                                             sheet: RawServoStyleSheetBorrowed,
                                             unique_id: u64);
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -232,16 +232,22 @@ impl ::selectors::SelectorImpl for Selec
     type LocalName = Atom;
     type NamespacePrefix = Atom;
     type NamespaceUrl = Namespace;
     type BorrowedNamespaceUrl = WeakNamespace;
     type BorrowedLocalName = WeakAtom;
 
     type PseudoElement = PseudoElement;
     type NonTSPseudoClass = NonTSPseudoClass;
+
+    #[inline]
+    fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool {
+        matches!(*pseudo_class, NonTSPseudoClass::Active |
+                                NonTSPseudoClass::Hover)
+    }
 }
 
 impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
     type Impl = SelectorImpl;
     type Error = StyleParseError<'i>;
 
     fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
                                  -> Result<NonTSPseudoClass, ParseError<'i>> {
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -67,17 +67,17 @@ use media_queries::Device;
 use properties::{ComputedValues, parse_style_attribute};
 use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
 use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty};
 use properties::style_structs::Font;
 use rule_tree::CascadeLevel as ServoCascadeLevel;
 use selector_parser::{AttrValue, ElementExt, PseudoClassStringArg};
 use selectors::Element;
 use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext};
 use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode};
 use shared_lock::Locked;
 use sink::Push;
 use smallvec::VecLike;
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
 use std::hash::{Hash, Hasher};
@@ -1419,28 +1419,25 @@ impl<'le> ::selectors::Element for Gecko
     fn get_namespace(&self) -> &WeakNamespace {
         unsafe {
             WeakNamespace::new(Gecko_Namespace(self.0))
         }
     }
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pseudo_class: &NonTSPseudoClass,
-                                    context: &mut MatchingContext,
+                                    context: &mut LocalMatchingContext<Self::Impl>,
                                     relevant_link: &RelevantLinkStatus,
                                     flags_setter: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         use selectors::matching::*;
         match *pseudo_class {
-            NonTSPseudoClass::AnyLink |
-            NonTSPseudoClass::Active |
             NonTSPseudoClass::Focus |
-            NonTSPseudoClass::Hover |
             NonTSPseudoClass::Enabled |
             NonTSPseudoClass::Disabled |
             NonTSPseudoClass::Checked |
             NonTSPseudoClass::Fullscreen |
             NonTSPseudoClass::Indeterminate |
             NonTSPseudoClass::PlaceholderShown |
             NonTSPseudoClass::Target |
             NonTSPseudoClass::Valid |
@@ -1472,22 +1469,29 @@ impl<'le> ::selectors::Element for Gecko
             NonTSPseudoClass::Default |
             NonTSPseudoClass::MozSubmitInvalid |
             NonTSPseudoClass::MozUIInvalid |
             NonTSPseudoClass::MozMeterOptimum |
             NonTSPseudoClass::MozMeterSubOptimum |
             NonTSPseudoClass::MozMeterSubSubOptimum |
             NonTSPseudoClass::MozAutofill |
             NonTSPseudoClass::MozAutofillPreview => {
-                // NB: It's important to use `intersect` instead of `contains`
-                // here, to handle `:any-link` correctly.
                 self.get_state().intersects(pseudo_class.state_flag())
             },
-            NonTSPseudoClass::Link => relevant_link.is_unvisited(self, context),
-            NonTSPseudoClass::Visited => relevant_link.is_visited(self, context),
+            NonTSPseudoClass::AnyLink => self.is_link(),
+            NonTSPseudoClass::Link => relevant_link.is_unvisited(self, context.shared),
+            NonTSPseudoClass::Visited => relevant_link.is_visited(self, context.shared),
+            NonTSPseudoClass::Active |
+            NonTSPseudoClass::Hover => {
+                if context.active_hover_quirk_matches() && !self.is_link() {
+                    false
+                } else {
+                    self.get_state().contains(pseudo_class.state_flag())
+                }
+            },
             NonTSPseudoClass::MozFirstNode => {
                 flags_setter(self, HAS_EDGE_CHILD_SELECTOR);
                 let mut elem = self.as_node();
                 while let Some(prev) = elem.prev_sibling() {
                     if prev.contains_non_whitespace_content() {
                         return false
                     }
                     elem = prev;
@@ -1517,19 +1521,23 @@ impl<'le> ::selectors::Element for Gecko
             NonTSPseudoClass::MozNativeAnonymous => unsafe {
                 Gecko_MatchesElement(pseudo_class.to_gecko_pseudoclasstype().unwrap(), self.0)
             },
             NonTSPseudoClass::MozIsHTML => {
                 self.is_html_element_in_html_document()
             }
             NonTSPseudoClass::MozPlaceholder => false,
             NonTSPseudoClass::MozAny(ref sels) => {
-                sels.iter().any(|s| {
+                let old_value = context.within_functional_pseudo_class_argument;
+                context.within_functional_pseudo_class_argument = true;
+                let result = sels.iter().any(|s| {
                     matches_complex_selector(s, 0, self, context, flags_setter)
-                })
+                });
+                context.within_functional_pseudo_class_argument = old_value;
+                result
             }
             NonTSPseudoClass::Lang(ref lang_arg) => {
                 self.match_element_lang(None, lang_arg)
             }
             NonTSPseudoClass::MozSystemMetric(ref s) |
             NonTSPseudoClass::MozLocaleDir(ref s) |
             NonTSPseudoClass::MozEmptyExceptChildrenWithLocalname(ref s) |
             NonTSPseudoClass::Dir(ref s) => {
@@ -1558,21 +1566,17 @@ impl<'le> ::selectors::Element for Gecko
         match self.implemented_pseudo_element() {
             Some(ref pseudo) => *pseudo == pseudo_element.canonical(),
             None => false,
         }
     }
 
     #[inline]
     fn is_link(&self) -> bool {
-        let mut context = MatchingContext::new(MatchingMode::Normal, None);
-        self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
-                                       &mut context,
-                                       &RelevantLinkStatus::default(),
-                                       &mut |_, _| {})
+        self.get_state().intersects(NonTSPseudoClass::AnyLink.state_flag())
     }
 
     fn get_id(&self) -> Option<Atom> {
         if !self.has_id() {
             return None;
         }
 
         let ptr = unsafe {
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -1016,17 +1016,18 @@ pub trait MatchMethods : TElement {
         } else {
             RuleInclusion::All
         };
 
         let bloom_filter = context.thread_local.bloom_filter.filter();
         let mut matching_context =
             MatchingContext::new_for_visited(MatchingMode::Normal,
                                              Some(bloom_filter),
-                                             visited_handling);
+                                             visited_handling,
+                                             context.shared.quirks_mode);
 
         {
             let smil_override = data.get_smil_override();
             let animation_rules = if self.may_have_animations() {
                 data.get_animation_rules()
             } else {
                 AnimationRules(None, None)
             };
@@ -1112,17 +1113,18 @@ pub trait MatchMethods : TElement {
             RuleInclusion::All
         };
 
         let bloom_filter = context.thread_local.bloom_filter.filter();
 
         let mut matching_context =
             MatchingContext::new_for_visited(MatchingMode::ForStatelessPseudoElement,
                                              Some(bloom_filter),
-                                             visited_handling);
+                                             visited_handling,
+                                             context.shared.quirks_mode);
 
         // Compute rule nodes for eagerly-cascaded pseudo-elements.
         let mut matches_different_pseudos = false;
         SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
             // For pseudo-elements, we only try to match visited rules if there
             // are also unvisited rules.  (This matches Gecko's behavior.)
             if visited_handling == VisitedHandlingMode::RelevantLinkVisited &&
                !data.styles().pseudos.has(&pseudo) {
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -15,17 +15,17 @@ use element_state::*;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::nsRestyleHint;
 #[cfg(feature = "servo")]
 use heapsize::HeapSizeOf;
 use selector_map::{SelectorMap, SelectorMapEntry};
 use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue};
 use selectors::Element;
 use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
 use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode, matches_selector};
 use selectors::parser::{AncestorHashes, Combinator, Component};
 use selectors::parser::{Selector, SelectorAndHashes, SelectorIter, SelectorMethods};
 use selectors::visitor::SelectorVisitor;
 use smallvec::SmallVec;
 use std::cell::Cell;
 use std::clone::Clone;
 use std::cmp;
@@ -659,17 +659,17 @@ fn dir_selector_to_state(s: &[u16]) -> E
 
 impl<'a, E> Element for ElementWrapper<'a, E>
     where E: TElement,
 {
     type Impl = SelectorImpl;
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pseudo_class: &NonTSPseudoClass,
-                                    context: &mut MatchingContext,
+                                    context: &mut LocalMatchingContext<Self::Impl>,
                                     relevant_link: &RelevantLinkStatus,
                                     _setter: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         // Some pseudo-classes need special handling to evaluate them against
         // the snapshot.
         match *pseudo_class {
@@ -702,20 +702,20 @@ impl<'a, E> Element for ElementWrapper<'
                 };
                 return state.contains(selector_flag);
             }
 
             // For :link and :visited, we don't actually want to test the element
             // state directly.  Instead, we use the `relevant_link` to determine if
             // they match.
             NonTSPseudoClass::Link => {
-                return relevant_link.is_unvisited(self, context);
+                return relevant_link.is_unvisited(self, context.shared);
             }
             NonTSPseudoClass::Visited => {
-                return relevant_link.is_visited(self, context);
+                return relevant_link.is_visited(self, context.shared);
             }
 
             #[cfg(feature = "gecko")]
             NonTSPseudoClass::MozTableBorderNonzero => {
                 if let Some(snapshot) = self.snapshot() {
                     if snapshot.has_other_pseudo_class_state() {
                         return snapshot.mIsTableBorderNonzero();
                     }
@@ -762,21 +762,17 @@ impl<'a, E> Element for ElementWrapper<'
                             pseudo_element: &PseudoElement,
                             context: &mut MatchingContext)
                             -> bool
     {
         self.element.match_pseudo_element(pseudo_element, context)
     }
 
     fn is_link(&self) -> bool {
-        let mut context = MatchingContext::new(MatchingMode::Normal, None);
-        self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
-                                       &mut context,
-                                       &RelevantLinkStatus::default(),
-                                       &mut |_, _| {})
+        self.element.is_link()
     }
 
     fn parent_element(&self) -> Option<Self> {
         self.element.parent_element()
             .map(|e| ElementWrapper::new(e, self.snapshot_map))
     }
 
     fn first_child_element(&self) -> Option<Self> {
@@ -1197,27 +1193,29 @@ impl DependencySet {
             // filter, and as such we may fast-reject selectors incorrectly.
             //
             // We may be able to improve this if we record as we go down the
             // tree whether any parent had a snapshot, and whether those
             // snapshots were taken due to an element class/id change, but it's
             // not clear we _need_ it right now.
             let mut then_context =
                 MatchingContext::new_for_visited(MatchingMode::Normal, None,
-                                                 VisitedHandlingMode::AllLinksUnvisited);
+                                                 VisitedHandlingMode::AllLinksUnvisited,
+                                                 shared_context.quirks_mode);
             let matched_then =
                 matches_selector(&dep.selector,
                                  dep.selector_offset,
                                  &dep.hashes,
                                  &snapshot_el,
                                  &mut then_context,
                                  &mut |_, _| {});
             let mut now_context =
                 MatchingContext::new_for_visited(MatchingMode::Normal, bloom_filter,
-                                                 VisitedHandlingMode::AllLinksUnvisited);
+                                                 VisitedHandlingMode::AllLinksUnvisited,
+                                                 shared_context.quirks_mode);
             let matches_now =
                 matches_selector(&dep.selector,
                                  dep.selector_offset,
                                  &dep.hashes,
                                  el,
                                  &mut now_context,
                                  &mut |_, _| {});
 
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -294,16 +294,22 @@ impl ::selectors::SelectorImpl for Selec
     type AttrValue = String;
     type Identifier = Atom;
     type ClassName = Atom;
     type LocalName = LocalName;
     type NamespacePrefix = Prefix;
     type NamespaceUrl = Namespace;
     type BorrowedLocalName = LocalName;
     type BorrowedNamespaceUrl = Namespace;
+
+    #[inline]
+    fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool {
+        matches!(*pseudo_class, NonTSPseudoClass::Active |
+                                NonTSPseudoClass::Hover)
+    }
 }
 
 impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
     type Impl = SelectorImpl;
     type Error = StyleParseError<'i>;
 
     fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
                                  -> Result<NonTSPseudoClass, ParseError<'i>> {
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -83,16 +83,17 @@ pub struct Stylist {
 
     /// Viewport constraints based on the current device.
     viewport_constraints: Option<ViewportConstraints>,
 
     /// Effective media query results cached from the last rebuild.
     effective_media_query_results: EffectiveMediaQueryResults,
 
     /// If true, the quirks-mode stylesheet is applied.
+    #[cfg_attr(feature = "servo", ignore_heap_size_of = "defined in selectors")]
     quirks_mode: QuirksMode,
 
     /// If true, the device has changed, and the stylist needs to be updated.
     is_device_dirty: bool,
 
     /// If true, the stylist is in a cleared state (e.g. just-constructed, or
     /// had clear() called on it with no following rebuild()).
     is_cleared: bool,
@@ -730,17 +731,19 @@ impl Stylist {
                     unsafe { p.set_selector_flags(parent_flags); }
                 }
             }
         };
 
         // Bug 1364242: We need to add visited support for lazy pseudos
         let mut declarations = ApplicableDeclarationList::new();
         let mut matching_context =
-            MatchingContext::new(MatchingMode::ForStatelessPseudoElement, None);
+            MatchingContext::new(MatchingMode::ForStatelessPseudoElement,
+                                 None,
+                                 self.quirks_mode);
         self.push_applicable_declarations(element,
                                           Some(&pseudo),
                                           None,
                                           None,
                                           AnimationRules(None, None),
                                           rule_inclusion,
                                           &mut declarations,
                                           &mut matching_context,
@@ -922,17 +925,17 @@ impl Stylist {
     /// treating us as an XBL stylesheet-only stylist.
     pub fn push_applicable_declarations_as_xbl_only_stylist<E, V>(&self,
                                                                   element: &E,
                                                                   applicable_declarations: &mut V)
         where E: TElement,
               V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
     {
         let mut matching_context =
-            MatchingContext::new(MatchingMode::Normal, None);
+            MatchingContext::new(MatchingMode::Normal, None, self.quirks_mode);
         let mut dummy_flag_setter = |_: &E, _: ElementSelectorFlags| {};
 
         self.element_map.author.get_all_matching_rules(element,
                                                        element,
                                                        applicable_declarations,
                                                        &mut matching_context,
                                                        &mut dummy_flag_setter,
                                                        CascadeLevel::XBL);
@@ -1151,17 +1154,17 @@ impl Stylist {
                                               flags_setter: &mut F)
                                               -> BitVec
         where E: TElement,
               F: FnMut(&E, ElementSelectorFlags),
     {
         // NB: `MatchingMode` doesn't really matter, given we don't share style
         // between pseudos.
         let mut matching_context =
-            MatchingContext::new(MatchingMode::Normal, bloom);
+            MatchingContext::new(MatchingMode::Normal, bloom, self.quirks_mode);
 
         // Note that, by the time we're revalidating, we're guaranteed that the
         // candidate and the entry have the same id, classes, and local name.
         // This means we're guaranteed to get the same rulehash buckets for all
         // the lookups, which means that the bitvecs are comparable. We verify
         // this in the caller by asserting that the bitvecs are same-length.
         let mut results = BitVec::new();
         self.selectors_for_cache_revalidation.lookup(*element, &mut |selector_and_hashes| {
--- a/servo/etc/ci/upload_docs.sh
+++ b/servo/etc/ci/upload_docs.sh
@@ -15,17 +15,19 @@ set -o pipefail
 cd "$(dirname ${0})/../.."
 
 ./mach doc
 # etc/doc.servo.org/index.html overwrites $(mach rust-root)/doc/index.html
 cp etc/doc.servo.org/* target/doc/
 
 ./mach cargo-geckolib doc
 mkdir target/doc/geckolib
-cp target/geckolib/doc/* target/doc/geckolib/
+# Use recursive copy here to avoid `cp` returning an error code
+# when it encounters directories.
+cp -r target/geckolib/doc/* target/doc/geckolib/
 
 python components/style/properties/build.py servo html regular
 
 cd components/script
 cmake .
 cmake --build . --target supported-apis
 cp apis.html ../../target/doc/servo/
 cd ../..
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -76,16 +76,17 @@ use style::gecko_bindings::structs::Iter
 use style::gecko_bindings::structs::MallocSizeOf;
 use style::gecko_bindings::structs::RawGeckoGfxMatrix4x4;
 use style::gecko_bindings::structs::RawGeckoPresContextOwned;
 use style::gecko_bindings::structs::ServoElementSnapshotTable;
 use style::gecko_bindings::structs::StyleRuleInclusion;
 use style::gecko_bindings::structs::URLExtraData;
 use style::gecko_bindings::structs::nsCSSValueSharedList;
 use style::gecko_bindings::structs::nsCompatibility;
+use style::gecko_bindings::structs::nsIDocument;
 use style::gecko_bindings::structs::nsStyleTransformMatrix::MatrixTransformOperator;
 use style::gecko_bindings::structs::nsresult;
 use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI, HasBoxFFI};
 use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
 use style::gecko_bindings::sugar::refptr::RefPtr;
 use style::gecko_properties::{self, style_structs};
 use style::media_queries::{MediaList, parse_media_query_list};
 use style::parallel;
@@ -1541,16 +1542,31 @@ pub extern "C" fn Servo_StyleSet_Clear(r
     data.clear_stylist();
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_Drop(data: RawServoStyleSetOwned) {
     let _ = data.into_box::<PerDocumentStyleData>();
 }
 
+
+/// Updating the stylesheets and redoing selector matching is always happens
+/// before the document element is inserted. Therefore we don't need to call
+/// `force_dirty` here.
+#[no_mangle]
+pub extern "C" fn Servo_StyleSet_CompatModeChanged(raw_data: RawServoStyleSetBorrowed) {
+    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
+    let quirks_mode = unsafe {
+        (*(*data.stylist.device().pres_context).mDocument
+                                               .raw::<nsIDocument>()).mCompatMode
+    };
+
+    data.stylist.set_quirks_mode(quirks_mode.into());
+}
+
 fn parse_property_into(declarations: &mut SourcePropertyDeclaration,
                        property_id: PropertyId,
                        value: *const nsACString,
                        data: *mut URLExtraData,
                        parsing_mode: structs::ParsingMode,
                        quirks_mode: QuirksMode) -> Result<(), ()> {
     use style::parser::ParsingMode;
     let value = unsafe { value.as_ref().unwrap().as_str_unchecked() };
--- a/testing/web-platform/meta/webdriver/navigation.py.ini
+++ b/testing/web-platform/meta/webdriver/navigation.py.ini
@@ -1,9 +1,10 @@
 [navigation.py]
   type: wdspec
+  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1368262
   expected: TIMEOUT
   [navigation.py::test_get_current_url_alert_prompt]
     expected: FAIL
 
   [navigation.py::test_set_malformed_url]
     expected: FAIL
 
--- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
@@ -15,19 +15,22 @@
 #include "nsUnicharUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsITreeBoxObject.h"
 #include "nsITreeColumns.h"
 #include "nsIObserverService.h"
 #include "nsIDOMKeyEvent.h"
 #include "mozilla/Services.h"
 #include "mozilla/ModuleUtils.h"
+#include "mozilla/Unused.h"
 
 static const char *kAutoCompleteSearchCID = "@mozilla.org/autocomplete/search;1?name=";
 
+using namespace mozilla;
+
 namespace {
 
 void
 SetTextValue(nsIAutoCompleteInput* aInput,
              const nsString& aValue,
              uint16_t aReason) {
   nsresult rv = aInput->SetTextValueWithReason(aValue, aReason);
   if (NS_FAILED(rv)) {
@@ -125,63 +128,52 @@ nsAutoCompleteController::SetInitiallySe
 
 NS_IMETHODIMP
 nsAutoCompleteController::SetInput(nsIAutoCompleteInput *aInput)
 {
   // Don't do anything if the input isn't changing.
   if (mInput == aInput)
     return NS_OK;
 
-  // Clear out the current search context
+  Unused << ResetInternalState();
   if (mInput) {
-    // Stop all searches in case they are async.
-    StopSearch();
-    ClearResults();
+    mSearches.Clear();
     ClosePopup();
-    mSearches.Clear();
   }
 
   mInput = aInput;
 
   // Nothing more to do if the input was just being set to null.
-  if (!aInput)
+  if (!mInput) {
     return NS_OK;
+  }
+  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
 
-  nsAutoString newValue;
-  aInput->GetTextValue(newValue);
+  // Reset the current search string.
+  input->GetTextValue(mSearchString);
 
   // Clear out this reference in case the new input's popup has no tree
   mTree = nullptr;
 
-  // Reset all search state members to default values
-  mSearchString = newValue;
-  mPlaceholderCompletionString.Truncate();
-  mDefaultIndexCompleted = false;
-  mProhibitAutoFill = false;
-  mSearchStatus = nsIAutoCompleteController::STATUS_NONE;
-  mRowCount = 0;
-  mSearchesOngoing = 0;
-  mCompletedSelectionIndex = -1;
-
   // Initialize our list of search objects
   uint32_t searchCount;
-  aInput->GetSearchCount(&searchCount);
+  input->GetSearchCount(&searchCount);
   mResults.SetCapacity(searchCount);
   mSearches.SetCapacity(searchCount);
   mImmediateSearchesCount = 0;
 
   const char *searchCID = kAutoCompleteSearchCID;
 
   // Since the controller can be used as a service it's important to reset this.
   mClearingAutoFillSearchesAgain = false;
 
   for (uint32_t i = 0; i < searchCount; ++i) {
     // Use the search name to create the contract id string for the search service
     nsAutoCString searchName;
-    aInput->GetSearchAt(i, searchName);
+    input->GetSearchAt(i, searchName);
     nsAutoCString cid(searchCID);
     cid.Append(searchName);
 
     // Use the created cid to get a pointer to the search service and store it for later
     nsCOMPtr<nsIAutoCompleteSearch> search = do_GetService(cid.get());
     if (search) {
       mSearches.AppendObject(search);
 
@@ -201,16 +193,39 @@ nsAutoCompleteController::SetInput(nsIAu
       }
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsAutoCompleteController::ResetInternalState()
+{
+  // Clear out the current search context
+  if (mInput) {
+    nsAutoString value;
+    mInput->GetTextValue(value);
+    // Stop all searches in case they are async.
+    Unused << StopSearch();
+    Unused << ClearResults();
+    mSearchString = value;
+  }
+
+  mPlaceholderCompletionString.Truncate();
+  mDefaultIndexCompleted = false;
+  mProhibitAutoFill = false;
+  mSearchStatus = nsIAutoCompleteController::STATUS_NONE;
+  mRowCount = 0;
+  mCompletedSelectionIndex = -1;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsAutoCompleteController::StartSearch(const nsAString &aSearchString)
 {
   mSearchString = aSearchString;
   StartSearches();
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -1538,18 +1553,17 @@ nsAutoCompleteController::EnterMatch(boo
             result->GetFinalCompleteValueAt(defaultIndex, value);
             break;
           }
         }
       }
     }
   }
 
-  nsCOMPtr<nsIObserverService> obsSvc =
-    mozilla::services::GetObserverService();
+  nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
   NS_ENSURE_STATE(obsSvc);
   obsSvc->NotifyObservers(input, "autocomplete-will-enter-text", nullptr);
 
   if (!value.IsEmpty()) {
     SetTextValue(input, value, nsIAutoCompleteInput::TEXTVALUE_REASON_ENTERMATCH);
     input->SelectTextRange(value.Length(), value.Length());
     mSearchString = value;
   }
@@ -1574,18 +1588,17 @@ nsAutoCompleteController::RevertTextValu
 
   nsAutoString oldValue(mSearchString);
   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
 
   bool cancel = false;
   input->OnTextReverted(&cancel);
 
   if (!cancel) {
-    nsCOMPtr<nsIObserverService> obsSvc =
-      mozilla::services::GetObserverService();
+    nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
     NS_ENSURE_STATE(obsSvc);
     obsSvc->NotifyObservers(input, "autocomplete-will-revert-text", nullptr);
 
     nsAutoString inputValue;
     input->GetTextValue(inputValue);
     // Don't change the value if it is the same to prevent sending useless events.
     // NOTE: how can |RevertTextValue| be called with inputValue != oldValue?
     if (!oldValue.Equals(inputValue)) {
--- a/toolkit/components/autocomplete/nsIAutoCompleteController.idl
+++ b/toolkit/components/autocomplete/nsIAutoCompleteController.idl
@@ -165,9 +165,16 @@ interface nsIAutoCompleteController : ns
    * This should be used when a search wants to pre-select an element before
    * the user starts using results.
    *
    * @note Setting this is not the same as just setting selectedIndex in
    * nsIAutocompletePopup, since this will take care of updating any internal
    * tracking variables of features like completeSelectedIndex.
    */
   void setInitiallySelectedIndex(in long index);
+
+  /*
+   * Reset controller internal caches for cases where the input doesn't change
+   * but its context resets, thus it is about to start a completely new search
+   * session.
+   */
+  void resetInternalState();
 };
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -1005,17 +1005,17 @@ class SchemaAPIManager extends EventEmit
     }
 
     this._checkLoadModule(module, name);
 
     Services.scriptloader.loadSubScript(module.url, this.global, "UTF-8");
 
     module.loaded = true;
 
-    return this._initModule(module, this.global[name]);
+    return this.global[name];
   }
   /**
    * aSynchronously loads an API module, if not already loaded, and
    * returns its ExtensionAPI constructor.
    *
    * @param {string} name
    *        The name of the module to load.
    *
@@ -1032,17 +1032,17 @@ class SchemaAPIManager extends EventEmit
 
     this._checkLoadModule(module, name);
 
     module.asyncLoaded = ChromeUtils.compileScript(module.url).then(script => {
       script.executeInGlobal(this.global);
 
       module.loaded = true;
 
-      return this._initModule(module, this.global[name]);
+      return this.global[name];
     });
 
     return module.asyncLoaded;
   }
 
   /**
    * Checks whether the given API module may be loaded for the given
    * extension, in the given scope.
@@ -1071,23 +1071,16 @@ class SchemaAPIManager extends EventEmit
 
     if (!Schemas.checkPermissions(module.namespaceName, extension)) {
       return false;
     }
 
     return true;
   }
 
-  _initModule(info, cls) {
-    cls.namespaceName = cls.namespaceName;
-    cls.scopes = new Set(info.scopes);
-
-    return cls;
-  }
-
   _checkLoadModule(module, name) {
     if (!module) {
       throw new Error(`Module '${name}' does not exist`);
     }
     if (module.asyncLoaded) {
       throw new Error(`Module '${name}' currently being lazily loaded`);
     }
     if (this.global[name]) {
--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -2214,20 +2214,23 @@ const LOADERS = {
   types: "loadType",
 };
 
 class Namespace extends Map {
   constructor(name, path) {
     super();
 
     this._lazySchemas = [];
+    this.initialized = false;
 
     this.name = name;
     this.path = name ? [...path, name] : [...path];
 
+    this.superNamespace = null;
+
     this.permissions = null;
     this.allowedContexts = [];
     this.defaultContexts = [];
   }
 
   /**
    * Adds a JSON Schema object to the set of schemas that represent this
    * namespace.
@@ -2239,27 +2242,35 @@ class Namespace extends Map {
   addSchema(schema) {
     this._lazySchemas.push(schema);
 
     for (let prop of ["permissions", "allowedContexts", "defaultContexts"]) {
       if (schema[prop]) {
         this[prop] = schema[prop];
       }
     }
+
+    if (schema.$import) {
+      this.superNamespace = Schemas.getNamespace(schema.$import);
+    }
   }
 
   /**
    * Initializes the keys of this namespace based on the schema objects
    * added via previous `addSchema` calls.
    */
-  init() { // eslint-disable-line complexity
-    if (!this._lazySchemas) {
+  init() {
+    if (this.initialized) {
       return;
     }
 
+    if (this.superNamespace) {
+      this._lazySchemas.unshift(...this.superNamespace._lazySchemas);
+    }
+
     for (let type of Object.keys(LOADERS)) {
       this[type] = new DefaultMap(() => []);
     }
 
     for (let schema of this._lazySchemas) {
       for (let type of schema.types || []) {
         if (!type.unsupported) {
           this.types.get(type.$extend || type.id).push(type);
@@ -2291,17 +2302,17 @@ class Namespace extends Map {
     // are later used to instantiate an Entry object based on the actual
     // schema object.
     for (let type of Object.keys(LOADERS)) {
       for (let key of this[type].keys()) {
         this.set(key, type);
       }
     }
 
-    this._lazySchemas = null;
+    this.initialized = true;
 
     if (DEBUG) {
       for (let key of this.keys()) {
         this.get(key);
       }
     }
   }
 
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
@@ -1294,16 +1294,94 @@ add_task(async function testNestedNamesp
   //    "Got the expected property defined in the CustomType instance)
   //
   // ok(instanceOfCustomType.onEvent &&
   //    instanceOfCustomType.onEvent.addListener &&
   //    typeof instanceOfCustomType.onEvent.addListener == "function",
   //    "Got the expected event defined in the CustomType instance");
 });
 
+let $importJson = [
+  {
+    namespace: "from_the",
+    $import: "future",
+  },
+  {
+    namespace: "future",
+    properties: {
+      PROP1: {value: "original value"},
+      PROP2: {value: "second original"},
+    },
+    types: [
+      {
+        id: "Colour",
+        type: "string",
+        enum: ["red", "white", "blue"],
+      },
+    ],
+    functions: [
+      {
+        name: "dye",
+        type: "function",
+        parameters: [
+          {name: "arg", $ref: "Colour"},
+        ],
+      },
+    ],
+  },
+  {
+    namespace: "embrace",
+    $import: "future",
+    properties: {
+      PROP2: {value: "overridden value"},
+    },
+    types: [
+      {
+        id: "Colour",
+        type: "string",
+        enum: ["blue", "orange"],
+      },
+    ],
+  },
+];
+
+add_task(async function test_$import() {
+  let url = "data:," + JSON.stringify($importJson);
+  await Schemas.load(url);
+
+  let root = {};
+  tallied = null;
+  Schemas.inject(root, wrapper);
+  equal(tallied, null);
+
+  equal(root.from_the.PROP1, "original value", "imported property");
+  equal(root.from_the.PROP2, "second original", "second imported property");
+  equal(root.from_the.Colour.RED, "red", "imported enum type");
+  equal(typeof root.from_the.dye, "function", "imported function");
+
+  root.from_the.dye("white");
+  verify("call", "from_the", "dye", ["white"]);
+
+  Assert.throws(() => root.from_the.dye("orange"),
+                /Invalid enumeration value/,
+                "original imported argument type Colour doesn't include 'orange'");
+
+  equal(root.embrace.PROP1, "original value", "imported property");
+  equal(root.embrace.PROP2, "overridden value", "overridden property");
+  equal(root.embrace.Colour.ORANGE, "orange", "overridden enum type");
+  equal(typeof root.embrace.dye, "function", "imported function");
+
+  root.embrace.dye("orange");
+  verify("call", "embrace", "dye", ["orange"]);
+
+  Assert.throws(() => root.embrace.dye("white"),
+                /Invalid enumeration value/,
+                "overridden argument type Colour doesn't include 'white'");
+});
+
 add_task(async function testLocalAPIImplementation() {
   let countGet2 = 0;
   let countProp3 = 0;
   let countProp3SubFoo = 0;
 
   let testingApiObj = {
     get PROP1() {
       // PROP1 is a schema-defined constant.
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -23,16 +23,18 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Integration.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadStore",
                                   "resource://gre/modules/DownloadStore.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport",
                                   "resource://gre/modules/DownloadImport.jsm");
@@ -630,39 +632,40 @@ this.DownloadIntegration = {
    *           the OS might still take a while until the file is actually
    *           launched.
    * @rejects  JavaScript exception if there was an error trying to launch
    *           the file.
    */
   async launchDownload(aDownload) {
     let file = new FileUtils.File(aDownload.target.path);
 
-#ifndef XP_WIN
-    // Ask for confirmation if the file is executable, except on Windows where
-    // the operating system will show the prompt based on the security zone.
-    // We do this here, instead of letting the caller handle the prompt
-    // separately in the user interface layer, for two reasons.  The first is
-    // because of its security nature, so that add-ons cannot forget to do
-    // this check.  The second is that the system-level security prompt would
-    // be displayed at launch time in any case.
-    if (file.isExecutable() &&
-        !(await this.confirmLaunchExecutable(file.path))) {
-      return;
-    }
-#endif
-
     // In case of a double extension, like ".tar.gz", we only
     // consider the last one, because the MIME service cannot
     // handle multiple extensions.
     let fileExtension = null, mimeInfo = null;
     let match = file.leafName.match(/\.([^.]+)$/);
     if (match) {
       fileExtension = match[1];
     }
 
+    let isWindowsExe = AppConstants.platform == "win" &&
+      fileExtension.toLowerCase() == "exe";
+
+    // Ask for confirmation if the file is executable, except for .exe on
+    // Windows where the operating system will show the prompt based on the
+    // security zone.  We do this here, instead of letting the caller handle
+    // the prompt separately in the user interface layer, for two reasons.  The
+    // first is because of its security nature, so that add-ons cannot forget
+    // to do this check.  The second is that the system-level security prompt
+    // would be displayed at launch time in any case.
+    if (file.isExecutable() && !isWindowsExe &&
+        !(await this.confirmLaunchExecutable(file.path))) {
+      return;
+    }
+
     try {
       // The MIME service might throw if contentType == "" and it can't find
       // a MIME type for the given extension, so we'll treat this case as
       // an unknown mimetype.
       mimeInfo = gMIMEService.getFromTypeAndExtension(aDownload.contentType,
                                                       fileExtension);
     } catch (e) { }
 
--- a/toolkit/content/widgets/datepicker.js
+++ b/toolkit/content/widgets/datepicker.js
@@ -280,18 +280,21 @@ function DatePicker(context) {
    *          {Function} setMonth
    *          {Function} getMonthString
    *          {Array<String>} datetimeOrders
    *        }
    * @param {DOMElement} context
    */
   function MonthYear(options, context) {
     const spinnerSize = 5;
-    const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric" }).format;
-    const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric", month: "long" }).format;
+    const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
+                                                                 timeZone: "UTC" }).format;
+    const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
+                                                                 month: "long",
+                                                                 timeZone: "UTC" }).format;
     const spinnerOrder =
       options.datetimeOrders.indexOf("month") < options.datetimeOrders.indexOf("year") ?
       "order-month-year" : "order-year-month";
 
     context.monthYearView.classList.add(spinnerOrder);
 
     this.context = context;
     this.state = { dateFormat };
@@ -307,17 +310,17 @@ function DatePicker(context) {
         viewportSize: spinnerSize
       }, context.monthYearView),
       year: new Spinner({
         id: "spinner-year",
         setValue: year => {
           this.state.isYearSet = true;
           options.setYear(year);
         },
-        getDisplayString: year => yearFormat(new Date(new Date(0).setFullYear(year))),
+        getDisplayString: year => yearFormat(new Date(new Date(0).setUTCFullYear(year))),
         viewportSize: spinnerSize
       }, context.monthYearView)
     };
 
     this._attachEventListeners();
   }
 
   MonthYear.prototype = {
--- a/toolkit/themes/shared/extensions/extensions.inc.css
+++ b/toolkit/themes/shared/extensions/extensions.inc.css
@@ -462,24 +462,32 @@ button.warning {
 
 .legacy-warning {
   background-color: #FFE900;
   color: #3E2800;
   padding: 4px 5px 3px;
   font-size: 0.9rem;
   font-weight: 600;
   -moz-user-focus: ignore;
+  transition-property: color, background-color;
+  transition-timing-function: cubic-bezier(.07,.95,0,1);
+  transition-duration: 150ms;
 }
 
 .legacy-warning:hover {
   background-color: #D7B600;
   color: #3E2800;
   text-decoration: none;
 }
 
+.legacy-warning:hover:active {
+  background-color: #a47f00;
+  color: #FFF;
+}
+
 #detail-view .legacy-warning {
   margin-top: 0.78rem;
 }
 
 .creator {
   font-weight: bold;
 }
 
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -2268,20 +2268,20 @@ nsNativeThemeCocoa::IsParentScrollbarRol
 {
   nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
   return nsLookAndFeel::UseOverlayScrollbars()
     ? CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
     : GetContentState(scrollbarFrame, NS_THEME_NONE).HasState(NS_EVENT_STATE_HOVER);
 }
 
 static bool
-IsHiDPIContext(nsPresContext* aContext)
+IsHiDPIContext(nsDeviceContext* aContext)
 {
   return nsPresContext::AppUnitsPerCSSPixel() >=
-    2 * aContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+    2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
 }
 
 NS_IMETHODIMP
 nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
                                          nsIFrame* aFrame,
                                          uint8_t aWidgetType,
                                          const nsRect& aRect,
                                          const nsRect& aDirtyRect)
@@ -2296,27 +2296,19 @@ nsNativeThemeCocoa::DrawWidgetBackground
   gfx::Rect nativeDirtyRect = NSRectToRect(aDirtyRect, p2a);
   gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
   nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
   float nativeWidgetHeight = round(nativeWidgetRect.Height());
   nativeWidgetRect.Round();
   if (nativeWidgetRect.IsEmpty())
     return NS_OK; // Don't attempt to draw invisible widgets.
 
-  // Bug 1059031 -
-  // Quartz theme drawing often adjusts drawing rects, so make
-  // sure our surface is big enough for that.
-  // For example, DrawCellWithSnapping snaps the drawing rect, and that can
-  // result in us drawing outside of what we thought the bounds were.
-  // The 5 is just a guess of how much margin we need to handle that.
-  nativeDirtyRect.Inflate(5);
-
   AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
 
-  bool hidpi = IsHiDPIContext(aFrame->PresContext());
+  bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
   if (hidpi) {
     // Use high-resolution drawing.
     nativeWidgetRect.Scale(0.5f);
     nativeWidgetHeight *= 0.5f;
     nativeDirtyRect.Scale(0.5f);
     aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(2.0f, 2.0f));
   }
 
@@ -3088,17 +3080,17 @@ nsNativeThemeCocoa::GetWidgetBorder(nsDe
       break;
     }
 
     case NS_THEME_STATUSBAR:
       aResult->SizeTo(1, 0, 0, 0);
       break;
   }
 
-  if (IsHiDPIContext(aFrame->PresContext())) {
+  if (IsHiDPIContext(aContext)) {
     *aResult = *aResult + *aResult; // doubled
   }
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
@@ -3125,17 +3117,17 @@ nsNativeThemeCocoa::GetWidgetPadding(nsD
   }
   return false;
 }
 
 bool
 nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
                                       uint8_t aWidgetType, nsRect* aOverflowRect)
 {
-  int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+  nsIntMargin overflow;
   switch (aWidgetType) {
     case NS_THEME_BUTTON:
     case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
     case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
     case NS_THEME_MAC_HELP_BUTTON:
     case NS_THEME_TOOLBARBUTTON:
     case NS_THEME_NUMBER_INPUT:
     case NS_THEME_TEXTFIELD:
@@ -3143,42 +3135,50 @@ nsNativeThemeCocoa::GetWidgetOverflow(ns
     case NS_THEME_SEARCHFIELD:
     case NS_THEME_LISTBOX:
     case NS_THEME_MENULIST:
     case NS_THEME_MENULIST_BUTTON:
     case NS_THEME_MENULIST_TEXTFIELD:
     case NS_THEME_CHECKBOX:
     case NS_THEME_RADIO:
     case NS_THEME_TAB:
+    case NS_THEME_FOCUS_OUTLINE:
     {
-      // We assume that the above widgets can draw a focus ring that will be less than
-      // or equal to 4 pixels thick.
-      nsIntMargin extraSize = nsIntMargin(kMaxFocusRingWidth,
-                                          kMaxFocusRingWidth,
-                                          kMaxFocusRingWidth,
-                                          kMaxFocusRingWidth);
-      nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
-                 NSIntPixelsToAppUnits(extraSize.right, p2a),
-                 NSIntPixelsToAppUnits(extraSize.bottom, p2a),
-                 NSIntPixelsToAppUnits(extraSize.left, p2a));
-      aOverflowRect->Inflate(m);
-      return true;
+      overflow.SizeTo(kMaxFocusRingWidth,
+                      kMaxFocusRingWidth,
+                      kMaxFocusRingWidth,
+                      kMaxFocusRingWidth);
+      break;
     }
     case NS_THEME_PROGRESSBAR:
     {
-      // Progress bars draw a 2 pixel white shadow under their progress indicators
-      nsMargin m(0, 0, NSIntPixelsToAppUnits(2, p2a), 0);
-      aOverflowRect->Inflate(m);
-      return true;
+      // Progress bars draw a 2 pixel white shadow under their progress indicators.
+      overflow.bottom = 2;
+      break;
+    }
+    case NS_THEME_METERBAR:
+    {
+      // Meter bars overflow their boxes by about 2 pixels.
+      overflow.SizeTo(2, 2, 2, 2);
+      break;
     }
-    case NS_THEME_FOCUS_OUTLINE:
-    {
-      aOverflowRect->Inflate(NSIntPixelsToAppUnits(2, p2a));
-      return true;
-    }
+  }
+
+  if (IsHiDPIContext(aContext)) {
+    // Double the number of device pixels.
+    overflow += overflow;
+  }
+
+  if (overflow != nsIntMargin()) {
+    int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+    aOverflowRect->Inflate(nsMargin(NSIntPixelsToAppUnits(overflow.top, p2a),
+                                    NSIntPixelsToAppUnits(overflow.right, p2a),
+                                    NSIntPixelsToAppUnits(overflow.bottom, p2a),
+                                    NSIntPixelsToAppUnits(overflow.left, p2a)));
+    return true;
   }
 
   return false;
 }
 
 static const int32_t kRegularScrollbarThumbMinSize = 26;
 static const int32_t kSmallScrollbarThumbMinSize = 26;
 
@@ -3492,17 +3492,17 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
       HIPoint pnt = { 0, 0 };
       HIRect bounds;
       HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
       aResult->SizeTo(bounds.size.width, bounds.size.height);
       *aIsOverridable = false;
     }
   }
 
-  if (IsHiDPIContext(aPresContext)) {
+  if (IsHiDPIContext(aPresContext->DeviceContext())) {
     *aResult = *aResult * 2;
   }
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }