Merge autoland to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 23 May 2017 18:01:59 -0400
changeset 360197 545ffce30eac33921dcb44b5a916c78c5cb0fc95
parent 360141 1ce55499e8193d53aeff91546b16e0c580ebd6f1 (current diff)
parent 360196 2b3e2531f9b5f3f220563d867f4414e17f5e546b (diff)
child 360283 db46d6c4f509c24049b1ce94d997b2822864a13f
push id31871
push userryanvm@gmail.com
push dateTue, 23 May 2017 22:02:07 +0000
treeherdermozilla-central@545ffce30eac [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
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 m-c. a=merge
browser/base/content/test/tabs/browser_tabSpinnerTypeProbe.js
browser/extensions/formautofill/test/unit/test_enabledStatus.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js
devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js
devtools/client/debugger/test/mochitest/doc_event-listeners-04.html
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -230,21 +230,17 @@ pref("general.autoScroll", false);
 #else
 pref("general.autoScroll", true);
 #endif
 
 // At startup, check if we're the default browser and prompt user if not.
 pref("browser.shell.checkDefaultBrowser", true);
 pref("browser.shell.shortcutFavicons",true);
 pref("browser.shell.mostRecentDateSetAsDefault", "");
-#ifdef RELEASE_OR_BETA
-pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", false);
-#else
 pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", true);
-#endif
 pref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun", false);
 pref("browser.shell.defaultBrowserCheckCount", 0);
 pref("browser.defaultbrowser.notificationbar", false);
 
 // 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
 // The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore
 pref("browser.startup.page",                1);
 pref("browser.startup.homepage",            "chrome://branding/locale/browserconfig.properties");
@@ -313,17 +309,17 @@ pref("browser.urlbar.restrict.searches",
 pref("browser.urlbar.match.title", "#");
 pref("browser.urlbar.match.url", "@");
 
 // The default behavior for the urlbar can be configured to use any combination
 // of the match filters with each additional filter adding more results (union).
 pref("browser.urlbar.suggest.history",              true);
 pref("browser.urlbar.suggest.bookmark",             true);
 pref("browser.urlbar.suggest.openpage",             true);
-pref("browser.urlbar.suggest.searches",             false);
+pref("browser.urlbar.suggest.searches",             true);
 pref("browser.urlbar.userMadeSearchSuggestionsChoice", false);
 // The suggestion opt-in notification will be shown on 4 different days.
 pref("browser.urlbar.daysBeforeHidingSuggestionsPrompt", 4);
 pref("browser.urlbar.lastSuggestionsPromptDate", 20160601);
 // The suggestion opt-out hint will be hidden after being shown 4 times.
 pref("browser.urlbar.timesBeforeHidingSuggestionsHint", 4);
 
 // Limit the number of characters sent to the current search engine to fetch
@@ -332,21 +328,17 @@ pref("browser.urlbar.maxCharsForSearchSu
 
 // Restrictions to current suggestions can also be applied (intersection).
 // Typed suggestion works only if history is set to true.
 pref("browser.urlbar.suggest.history.onlyTyped",    false);
 
 pref("browser.urlbar.formatting.enabled", true);
 pref("browser.urlbar.trimURLs", true);
 
-#if defined(NIGHTLY_BUILD)
 pref("browser.urlbar.oneOffSearches", true);
-#else
-pref("browser.urlbar.oneOffSearches", false);
-#endif
 
 // If changed to true, copying the entire URL from the location bar will put the
 // human readable (percent-decoded) URL on the clipboard.
 pref("browser.urlbar.decodeURLsOnCopy", false);
 
 pref("browser.altClickSave", false);
 
 // Enable logging downloads operations to the Console.
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -219,17 +219,17 @@ toolbar[overflowable] > .customization-t
   overflow: hidden;
 }
 
 toolbar:not([overflowing]) > .overflow-button,
 toolbar[customizing] > .overflow-button {
   display: none;
 }
 
-#nav-bar[nonemptyoverflow] > .overflow-button,
+window:not([chromehidden~="toolbar"]) #nav-bar[nonemptyoverflow] > .overflow-button,
 #nav-bar[customizing][photon-structure] > .overflow-button {
   display: -moz-box;
 }
 
 /* The ids are ugly, but this should be reasonably performant, and
  * using a tagname as the last item would be less so.
  */
 #widget-overflow-list:empty + #widget-overflow-fixed-separator,
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5683,29 +5683,35 @@ const nodeToTooltipMap = {
   "downloads-button": "downloads.tooltip",
   "fullscreen-button": "fullscreenButton.tooltip",
   "new-window-button": "newWindowButton.tooltip",
   "new-tab-button": "newTabButton.tooltip",
   "tabs-newtab-button": "newTabButton.tooltip",
   "reload-button": "reloadButton.tooltip",
   "stop-button": "stopButton.tooltip",
   "urlbar-zoom-button": "urlbar-zoom-button.tooltip",
+  "appMenu-cut-button": "cut-button.tooltip",
+  "appMenu-copy-button": "copy-button.tooltip",
+  "appMenu-paste-button": "paste-button.tooltip",
 };
 const nodeToShortcutMap = {
   "bookmarks-menu-button": "manBookmarkKb",
   "context-reload": "key_reload",
   "context-stop": "key_stop",
   "downloads-button": "key_openDownloads",
   "fullscreen-button": "key_fullScreen",
   "new-window-button": "key_newNavigator",
   "new-tab-button": "key_newNavigatorTab",
   "tabs-newtab-button": "key_newNavigatorTab",
   "reload-button": "key_reload",
   "stop-button": "key_stop",
   "urlbar-zoom-button": "key_fullZoomReset",
+  "appMenu-cut-button": "key_cut",
+  "appMenu-copy-button": "key_copy",
+  "appMenu-paste-button": "key_paste",
 };
 
 if (AppConstants.platform == "macosx") {
   nodeToTooltipMap["print-button"] = "printButton.tooltip";
   nodeToShortcutMap["print-button"] = "printKb";
 }
 
 const gDynamicTooltipCache = new Map();
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -92,24 +92,20 @@ tabpanels {
  * memory that to-be-restored tabs would otherwise consume simply by setting
  * their browsers to 'display: none' as that will prevent them from having to
  * create a presentation and the like.
  */
 browser[pending] {
   display: none;
 }
 
-browser[pendingtabchild],
+browser[blank],
 browser[pendingpaint] {
   opacity: 0;
 }
 
-tabbrowser[pendingtabchild] {
-  background-color: #ffffff !important;
-}
-
 tabbrowser[pendingpaint] {
   background-image: url(chrome://browser/skin/tabbrowser/pendingpaint.png);
   background-repeat: no-repeat;
   background-position: center center;
   background-color: #f9f9f9 !important;
   background-size: 30px;
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1241,36 +1241,20 @@
                   if (prompts.length) {
                     // NB: This code assumes that the beforeunload prompt
                     //     is the top-most prompt on the tab.
                     prompts[prompts.length - 1].abortPrompt();
                   }
                 });
               }
 
-              oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
-              if (this.isFindBarInitialized(oldTab)) {
-                let findBar = this.getFindBar(oldTab);
-                oldTab._findBarFocused = (!findBar.hidden &&
-                  findBar._findField.getAttribute("focused") == "true");
+              if (!gMultiProcessBrowser) {
+                this._adjustFocusBeforeTabSwitch(oldTab, this.mCurrentTab);
+                this._adjustFocusAfterTabSwitch(this.mCurrentTab);
               }
-
-              // If focus is in the tab bar, retain it there.
-              if (document.activeElement == oldTab) {
-                // We need to explicitly focus the new tab, because
-                // tabbox.xml does this only in some cases.
-                this.mCurrentTab.focus();
-              } else if (gMultiProcessBrowser && document.activeElement !== newBrowser) {
-                // Clear focus so that _adjustFocusAfterTabSwitch can detect if
-                // some element has been focused and respect that.
-                document.activeElement.blur();
-              }
-
-              if (!gMultiProcessBrowser)
-                this._adjustFocusAfterTabSwitch(this.mCurrentTab);
             }
 
             updateUserContextUIIndicator();
             gIdentityHandler.updateSharingIndicator();
 
             this.tabContainer._setPositionalAttributes();
 
             if (!gMultiProcessBrowser) {
@@ -1284,16 +1268,55 @@
             }
 
             if (!aForceUpdate)
               TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
           ]]>
         </body>
       </method>
 
+      <method name="_adjustFocusBeforeTabSwitch">
+        <parameter name="oldTab"/>
+        <parameter name="newTab"/>
+        <body><![CDATA[
+          if (this._previewMode) {
+            return;
+          }
+
+          let oldBrowser = oldTab.linkedBrowser;
+          let newBrowser = newTab.linkedBrowser;
+
+          oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
+
+          if (this.isFindBarInitialized(oldTab)) {
+            let findBar = this.getFindBar(oldTab);
+            oldTab._findBarFocused = (!findBar.hidden &&
+              findBar._findField.getAttribute("focused") == "true");
+          }
+
+          // If focus is in the tab bar, retain it there.
+          if (document.activeElement == oldTab) {
+            // We need to explicitly focus the new tab, because
+            // tabbox.xml does this only in some cases.
+            newTab.focus();
+          } else if (gMultiProcessBrowser && document.activeElement !== newBrowser) {
+
+            let keepFocusOnUrlBar = newBrowser &&
+                                    newBrowser._urlbarFocused &&
+                                    gURLBar &&
+                                    gURLBar.focused;
+            if (!keepFocusOnUrlBar) {
+              // Clear focus so that _adjustFocusAfterTabSwitch can detect if
+              // some element has been focused and respect that.
+              document.activeElement.blur();
+            }
+          }
+        ]]></body>
+      </method>
+
       <method name="_adjustFocusAfterTabSwitch">
         <parameter name="newTab"/>
         <body><![CDATA[
         // Don't steal focus from the tab bar.
         if (document.activeElement == newTab)
           return;
 
         let newBrowser = this.getBrowserForTab(newTab);
@@ -1309,22 +1332,25 @@
 
         // Focus the location bar if it was previously focused for that tab.
         // In full screen mode, only bother making the location bar visible
         // if the tab is a blank one.
         if (newBrowser._urlbarFocused && gURLBar) {
           // Explicitly close the popup if the URL bar retains focus
           gURLBar.closePopup();
 
-          if (!window.fullScreen) {
-            gURLBar.focus();
+          // If the user happened to type into the URL bar for this browser
+          // by the time we got here, focusing will cause the text to be
+          // selected which could cause them to overwrite what they've
+          // already typed in.
+          if (gURLBar.focused && newBrowser.userTypedValue) {
             return;
           }
 
-          if (isTabEmpty(this.mCurrentTab)) {
+          if (!window.fullScreen || isTabEmpty(this.mCurrentTab)) {
             focusAndSelectUrlBar();
             return;
           }
         }
 
         // Focus the find bar if it was previously focused for that tab.
         if (gFindBarInitialized && !gFindBar.hidden &&
             this.selectedTab._findBarFocused) {
@@ -1526,16 +1552,17 @@
             var aNoReferrer;
             var aUserContextId;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aOpener;
             var aIsPrerendered;
             var aCreateLazyBrowser;
             var aNextTabParentId;
+            var aFocusUrlBar;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aTriggeringPrincipal      = params.triggeringPrincipal
               aReferrerURI              = params.referrerURI;
               aReferrerPolicy           = params.referrerPolicy;
               aCharset                  = params.charset;
@@ -1551,16 +1578,17 @@
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aOpener                   = params.opener;
               aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aNextTabParentId          = params.nextTabParentId;
+              aFocusUrlBar              = params.focusUrlBar;
             }
 
             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
             var owner = bgLoad ? null : this.selectedTab;
 
             var tab = this.addTab(aURI, {
                                   triggeringPrincipal: aTriggeringPrincipal,
@@ -1578,17 +1606,18 @@
                                   createLazyBrowser: aCreateLazyBrowser,
                                   preferredRemoteType: aPreferredRemoteType,
                                   noReferrer: aNoReferrer,
                                   userContextId: aUserContextId,
                                   originPrincipal: aOriginPrincipal,
                                   sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                   opener: aOpener,
                                   isPrerendered: aIsPrerendered,
-                                  nextTabParentId: aNextTabParentId });
+                                  nextTabParentId: aNextTabParentId,
+                                  focusUrlBar: aFocusUrlBar });
             if (!bgLoad)
               this.selectedTab = tab;
 
             return tab;
          ]]>
         </body>
       </method>
 
@@ -2283,16 +2312,17 @@
             var aOriginPrincipal;
             var aDisallowInheritPrincipal;
             var aOpener;
             var aIsPrerendered;
             var aCreateLazyBrowser;
             var aSkipBackgroundNotify;
             var aNextTabParentId;
             var aNoInitialLabel;
+            var aFocusUrlBar;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aTriggeringPrincipal      = params.triggeringPrincipal;
               aReferrerURI              = params.referrerURI;
               aReferrerPolicy           = params.referrerPolicy;
               aCharset                  = params.charset;
@@ -2312,16 +2342,17 @@
               aOriginPrincipal          = params.originPrincipal;
               aDisallowInheritPrincipal = params.disallowInheritPrincipal;
               aOpener                   = params.opener;
               aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aSkipBackgroundNotify     = params.skipBackgroundNotify;
               aNextTabParentId          = params.nextTabParentId;
               aNoInitialLabel           = params.noInitialLabel;
+              aFocusUrlBar              = params.focusUrlBar;
             }
 
             // if we're adding tabs, we're past interrupt mode, ditch the owner
             if (this.mCurrentTab.owner)
               this.mCurrentTab.owner = null;
 
             var t = document.createElementNS(NS_XUL, "tab");
 
@@ -2373,16 +2404,19 @@
             // When overflowing, new tabs are scrolled into view smoothly, which
             // doesn't go well together with the width transition. So we skip the
             // transition in that case.
             let animate = !aSkipAnimation &&
                           this.tabContainer.getAttribute("overflow") != "true" &&
                           Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled");
             if (!animate) {
               t.setAttribute("fadein", "true");
+
+              // Call _handleNewTab asynchronously as it needs to know if the
+              // new tab is selected.
               setTimeout(function(tabContainer) {
                 tabContainer._handleNewTab(t);
               }, 0, this.tabContainer);
             }
 
             // invalidate cache
             this._visibleTabs = null;
 
@@ -2435,16 +2469,21 @@
                                         userContextId: aUserContextId,
                                         sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                         opener: aOpener,
                                         isPrerendered: aIsPrerendered,
                                         nextTabParentId: aNextTabParentId });
             }
 
             t.linkedBrowser = b;
+
+            if (aFocusUrlBar) {
+              b._urlbarFocused = true;
+            }
+
             this._tabForBrowser.set(b, t);
             t.permanentKey = b.permanentKey;
             t._browserParams = { uriIsAboutBlank,
                                  remoteType,
                                  usingPreloadedContent };
 
             // If the caller opts in, create a lazy browser.
             if (aCreateLazyBrowser) {
@@ -3831,17 +3870,16 @@
             return this._switcher;
           }
 
           let switcher = {
             // How long to wait for a tab's layers to load. After this
             // time elapses, we're free to put up the spinner and start
             // trying to load a different tab.
             TAB_SWITCH_TIMEOUT: 400 /* ms */,
-            NEWNESS_THRESHOLD: 1000 /* ms */,
 
             // When the user hasn't switched tabs for this long, we unload
             // layers for all tabs that aren't in use.
             UNLOAD_DELAY: 300 /* ms */,
 
             // The next three tabs form the principal state variables.
             // See the assertions in postActions for their invariants.
 
@@ -3863,22 +3901,16 @@
 
             tabbrowser: this,  // Reference to gBrowser.
             loadTimer: null,   // TAB_SWITCH_TIMEOUT nsITimer instance.
             unloadTimer: null, // UNLOAD_DELAY nsITimer instance.
 
             // Map from tabs to STATE_* (below).
             tabState: new Map(),
 
-            // Holds a collection of <xul:browser>'s for this tabbrowser
-            // that we cannot force paint since their TabChild's haven't
-            // been constructed yet. Instead, we show blank tabs for them
-            // when attempting to switch to them.
-            pendingTabChild: new WeakSet(),
-
             // True if we're in the midst of switching tabs.
             switchInProgress: false,
 
             // Keep an exact list of content processes (tabParent) in which
             // we're actively suppressing the display port. This gives a robust
             // way to make sure we don't forget to un-suppress.
             activeSuppressDisplayport: new Set(),
 
@@ -3943,16 +3975,29 @@
                   this.onLayersReady(browser);
                 }
               } else if (state == this.STATE_UNLOADING) {
                 browser.docShellIsActive = false;
                 if (!tabParent) {
                   this.onLayersCleared(browser);
                 }
               }
+
+              if (!tab.linkedBrowser.isRemoteBrowser) {
+                // setTabState is potentially re-entrant in the non-remote case,
+                // so we must re-get the state for this assertion.
+                let nonRemoteState = this.getTabState(tab);
+                // Non-remote tabs can never stay in the STATE_LOADING
+                // or STATE_UNLOADING states. By the time this function
+                // exits, a non-remote tab must be in STATE_LOADED or
+                // STATE_UNLOADED, since the painting and the layer
+                // upload happen synchronously.
+                this.assert(nonRemoteState == this.STATE_UNLOADED ||
+                            nonRemoteState == this.STATE_LOADED);
+              }
             },
 
             get minimized() {
               return window.windowState == window.STATE_MINIMIZED;
             },
 
             init() {
               this.log("START");
@@ -3964,17 +4009,16 @@
 
               window.addEventListener("MozAfterPaint", this);
               window.addEventListener("MozLayerTreeReady", this);
               window.addEventListener("MozLayerTreeCleared", this);
               window.addEventListener("TabRemotenessChange", this);
               window.addEventListener("sizemodechange", this);
               window.addEventListener("SwapDocShells", this, true);
               window.addEventListener("EndSwapDocShells", this, true);
-              window.addEventListener("MozTabChildNotReady", this, true);
 
               let tab = this.requestedTab;
               let browser = tab.linkedBrowser;
               let tabIsLoaded = !browser.isRemoteBrowser ||
                                 browser.frameLoader.tabParent.hasPresented;
 
               if (!this.minimized) {
                 this.log("Initial tab is loaded?: " + tabIsLoaded);
@@ -3995,17 +4039,16 @@
 
               window.removeEventListener("MozAfterPaint", this);
               window.removeEventListener("MozLayerTreeReady", this);
               window.removeEventListener("MozLayerTreeCleared", this);
               window.removeEventListener("TabRemotenessChange", this);
               window.removeEventListener("sizemodechange", this);
               window.removeEventListener("SwapDocShells", this, true);
               window.removeEventListener("EndSwapDocShells", this, true);
-              window.removeEventListener("MozTabChildNotReady", this, true);
 
               this.tabbrowser._switcher = null;
 
               this.activeSuppressDisplayport.forEach(function(tabParent) {
                 tabParent.suppressDisplayport(false);
               });
               this.activeSuppressDisplayport.clear();
             },
@@ -4022,18 +4065,16 @@
               this.assert(this.lastVisibleTab === this.requestedTab);
               this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
 
               this.destroy();
 
               let toBrowser = this.requestedTab.linkedBrowser;
               toBrowser.setAttribute("primary", "true");
 
-              this.tabbrowser._adjustFocusAfterTabSwitch(this.requestedTab);
-
               let fromBrowser = this.originalTab.linkedBrowser;
               // It's possible that the tab we're switching from closed
               // before we were able to finalize, in which case, fromBrowser
               // doesn't exist.
               if (fromBrowser) {
                 fromBrowser.removeAttribute("primary");
               }
 
@@ -4045,20 +4086,36 @@
               });
               this.tabbrowser.dispatchEvent(event);
             },
 
             // This function is called after all the main state changes to
             // make sure we display the right tab.
             updateDisplay() {
               let requestedTabState = this.getTabState(this.requestedTab);
-
-              let shouldBeBlank =
-                this.pendingTabChild.has(this.requestedTab.linkedBrowser) &&
-                requestedTabState == this.STATE_LOADING;
+              let requestedBrowser = this.requestedTab.linkedBrowser;
+
+              // It is more desirable to show a blank tab when appropriate than
+              // the tab switch spinner - especially since the spinner is usually
+              // preceded by a perceived lag of TAB_SWITCH_TIMEOUT ms in the
+              // tab switch. We can hide this lag, and hide the time being spent
+              // constructing TabChild's, layer trees, etc, by showing a blank
+              // tab instead and focusing it immediately.
+              let shouldBeBlank = false;
+              if (requestedBrowser.isRemoteBrowser) {
+                // If a tab is remote, we can show a blank tab instead of a
+                // spinner if we know it has never presented before, or if it
+                // has just crashed and we haven't started showing the tab crashed
+                // page yet.
+                let fl = requestedBrowser.frameLoader;
+                shouldBeBlank = (!fl.tabParent || !fl.tabParent.hasPresented);
+              }
+
+              this.log("Tab should be blank: " + shouldBeBlank);
+              this.log("Requested tab is remote?: " + requestedBrowser.isRemoteBrowser);
 
               // Figure out which tab we actually want visible right now.
               let showTab = null;
               if (requestedTabState != this.STATE_LOADED &&
                   this.lastVisibleTab && this.loadTimer && !shouldBeBlank) {
                 // If we can't show the requestedTab, and lastVisibleTab is
                 // available, show it.
                 showTab = this.lastVisibleTab;
@@ -4066,26 +4123,24 @@
                 // Show the requested tab. If it's not available, we'll show the spinner or a blank tab.
                 showTab = this.requestedTab;
               }
 
               // First, let's deal with blank tabs, which we show instead
               // of the spinner when the tab is not currently set up
               // properly in the content process.
               if (!shouldBeBlank && this.blankTab) {
-                this.tabbrowser.removeAttribute("pendingtabchild");
-                this.blankTab.linkedBrowser.removeAttribute("pendingtabchild");
+                this.blankTab.linkedBrowser.removeAttribute("blank");
                 this.blankTab = null;
               } else if (shouldBeBlank && this.blankTab !== showTab) {
                 if (this.blankTab) {
-                  this.blankTab.linkedBrowser.removeAttribute("pendingtabchild");
+                  this.blankTab.linkedBrowser.removeAttribute("blank");
                 }
                 this.blankTab = showTab;
-                this.tabbrowser.setAttribute("pendingtabchild", "true");
-                this.blankTab.linkedBrowser.setAttribute("pendingtabchild", "true");
+                this.blankTab.linkedBrowser.setAttribute("blank", "true");
               }
 
               // Show or hide the spinner as needed.
               let needSpinner = this.getTabState(showTab) != this.STATE_LOADED &&
                                 !this.minimized &&
                                 !shouldBeBlank;
 
               if (!needSpinner && this.spinnerTab) {
@@ -4101,16 +4156,17 @@
                 }
                 this.spinnerTab = showTab;
                 this.tabbrowser.setAttribute("pendingpaint", "true");
                 this.spinnerTab.linkedBrowser.setAttribute("pendingpaint", "true");
               }
 
               // Switch to the tab we've decided to make visible.
               if (this.visibleTab !== showTab) {
+                this.tabbrowser._adjustFocusBeforeTabSwitch(this.visibleTab, showTab);
                 this.visibleTab = showTab;
 
                 this.maybeVisibleTabs.add(showTab);
 
                 let tabs = this.tabbrowser.mTabBox.tabs;
                 let tabPanel = this.tabbrowser.mPanelContainer;
                 let showPanel = tabs.getRelatedElement(showTab);
                 let index = Array.indexOf(tabPanel.childNodes, showPanel);
@@ -4196,16 +4252,28 @@
               this.assert(!this.loadingTab ||
                           this.getTabState(this.loadingTab) == this.STATE_LOADING);
 
               // We guarantee that loadingTab is non-null iff loadTimer is non-null. So
               // the timer is set only when we're loading something.
               this.assert(!this.loadTimer || this.loadingTab);
               this.assert(!this.loadingTab || this.loadTimer);
 
+              // If we're switching to a non-remote tab, there's no need to wait
+              // for it to send layers to the compositor, as this will happen
+              // synchronously. Clearing this here means that in the next step,
+              // we can load the non-remote browser immediately.
+              if (!this.requestedTab.linkedBrowser.isRemoteBrowser) {
+                this.loadingTab = null;
+                if (this.loadTimer) {
+                  this.clearTimer(this.loadTimer);
+                  this.loadTimer = null;
+                }
+              }
+
               // If we're not loading anything, try loading the requested tab.
               let requestedState = this.getTabState(this.requestedTab);
               if (!this.loadTimer && !this.minimized &&
                   (requestedState == this.STATE_UNLOADED ||
                    requestedState == this.STATE_UNLOADING)) {
                 this.loadRequestedTab();
               }
 
@@ -4229,16 +4297,20 @@
 
               // It's possible for updateDisplay to trigger one of our own event
               // handlers, which might cause finish() to already have been called.
               // Check for that before calling finish() again.
               if (!this.tabbrowser._switcher) {
                 return;
               }
 
+              if (this.blankTab) {
+                this.maybeFinishTabSwitch();
+              }
+
               if (numPending == 0) {
                 this.finish();
               }
 
               this.logState("done");
             },
 
             // Fires when we're ready to unload unused tabs.
@@ -4282,17 +4354,16 @@
               this.preActions();
               this.loadTimer = null;
               this.loadingTab = null;
               this.postActions();
             },
 
             // Fires when the layers become available for a tab.
             onLayersReady(browser) {
-              this.pendingTabChild.delete(browser);
               let tab = this.tabbrowser.getTabForBrowser(browser);
               this.logState(`onLayersReady(${tab._tPos}, ${browser.isRemoteBrowser})`);
 
               this.assert(this.getTabState(tab) == this.STATE_LOADING ||
                           this.getTabState(tab) == this.STATE_LOADED);
               this.setTabState(tab, this.STATE_LOADED);
 
               this.maybeFinishTabSwitch();
@@ -4309,17 +4380,16 @@
             // around.
             onPaint() {
               this.maybeVisibleTabs.clear();
               this.maybeFinishTabSwitch();
             },
 
             // Called when we're done clearing the layers for a tab.
             onLayersCleared(browser) {
-              this.pendingTabChild.delete(browser);
               let tab = this.tabbrowser.getTabForBrowser(browser);
               if (tab) {
                 this.logState(`onLayersCleared(${tab._tPos})`);
                 this.assert(this.getTabState(tab) == this.STATE_UNLOADING ||
                             this.getTabState(tab) == this.STATE_UNLOADED);
                 this.setTabState(tab, this.STATE_UNLOADED);
               }
             },
@@ -4333,21 +4403,17 @@
                 if (this.getTabState(tab) == this.STATE_LOADING) {
                   this.onLayersReady(tab.linkedBrowser);
                 } else if (this.getTabState(tab) == this.STATE_UNLOADING) {
                   this.onLayersCleared(tab.linkedBrowser);
                 }
               } else if (this.getTabState(tab) == this.STATE_LOADED) {
                 // A tab just changed from non-remote to remote, which means
                 // that it's gone back into the STATE_LOADING state until
-                // it sends up a layer tree. We also add the browser to
-                // the pendingTabChild set since this browser is unlikely
-                // to have its TabChild set up right away, and we want to
-                // make it appear "blank" instead of showing a spinner for it.
-                this.pendingTabChild.add(tab.linkedBrowser);
+                // it sends up a layer tree.
                 this.setTabState(tab, this.STATE_LOADING);
               }
             },
 
             // Called when a tab has been removed, and the browser node is
             // about to be removed from the DOM.
             onTabRemoved(tab) {
               if (this.lastVisibleTab == tab) {
@@ -4385,40 +4451,31 @@
 
             onSwapDocShells(ourBrowser, otherBrowser) {
               // This event fires before the swap. ourBrowser is from
               // our window. We save the state of otherBrowser since ourBrowser
               // needs to take on that state at the end of the swap.
 
               let otherTabbrowser = otherBrowser.ownerGlobal.gBrowser;
               let otherState;
-              let pendingTabChild = false;
               if (otherTabbrowser && otherTabbrowser._switcher) {
                 let otherTab = otherTabbrowser.getTabForBrowser(otherBrowser);
                 let otherSwitcher = otherTabbrowser._switcher;
                 otherState = otherSwitcher.getTabState(otherTab);
-                pendingTabChild = otherSwitcher.pendingTabChild.has(otherBrowser);
-
-                if (pendingTabChild) {
-                  this.assert(otherState == this.STATE_LOADING);
-                }
-
-                otherSwitcher.pendingTabChild.delete(otherBrowser);
               } else {
                 otherState = (otherBrowser.docShellIsActive
                               ? this.STATE_LOADED
                               : this.STATE_UNLOADED);
               }
 
               if (!this.swapMap) {
                 this.swapMap = new WeakMap();
               }
               this.swapMap.set(otherBrowser, {
                 state: otherState,
-                pendingTabChild,
               });
             },
 
             onEndSwapDocShells(ourBrowser, otherBrowser) {
               // The swap has happened. We reset the loadingTab in
               // case it has been swapped. We also set ourBrowser's state
               // to whatever otherBrowser's state was before the swap.
 
@@ -4428,76 +4485,51 @@
                 // ready yet. Typically it will already be ready
                 // though. If it's not, we're probably in a new window,
                 // in which case we have no other tabs to display anyway.
                 this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
               }
               this.loadingTab = null;
 
-              let { state: otherState, pendingTabChild } =
-                this.swapMap.get(otherBrowser);
+              let { state: otherState } = this.swapMap.get(otherBrowser);
 
               this.swapMap.delete(otherBrowser);
 
               let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser);
               if (ourTab) {
                 this.setTabStateNoAction(ourTab, otherState);
-                if (pendingTabChild) {
-                  this.pendingTabChild.add(ourTab.linkedBrowser);
-                }
               }
             },
 
             shouldActivateDocShell(browser) {
               let tab = this.tabbrowser.getTabForBrowser(browser);
               let state = this.getTabState(tab);
               return state == this.STATE_LOADING || state == this.STATE_LOADED;
             },
 
             activateBrowserForPrintPreview(browser) {
               let tab = this.tabbrowser.getTabForBrowser(browser);
               this.setTabState(tab, this.STATE_LOADING);
             },
 
-            // The tab for this browser isn't currently set
-            // up in the content process, so we have no chance
-            // of painting it right away. We'll paint a blank
-            // tab instead.
-            onTabChildNotReady(browser) {
-              this.assert(browser.isRemoteBrowser);
-
-              let tab = this.tabbrowser.getTabForBrowser(browser);
-
-              this.assert(this.getTabState(tab) == this.STATE_LOADING);
-
-              this.logState(`onTabChildNotReady(${tab._tPos})`);
-              this.pendingTabChild.add(browser);
-              this.maybeFinishTabSwitch();
-
-              if (this.loadingTab === tab) {
-                this.clearTimer(this.loadTimer);
-                this.loadTimer = null;
-                this.loadingTab = null;
-              }
-            },
-
             // Called when the user asks to switch to a given tab.
             requestTab(tab) {
               if (tab === this.requestedTab) {
                 return;
               }
 
               this.logState("requestTab " + this.tinfo(tab));
               this.startTabSwitch();
 
               this.requestedTab = tab;
 
               let browser = this.requestedTab.linkedBrowser;
               let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+
               if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
                 fl.tabParent.suppressDisplayport(true);
                 this.activeSuppressDisplayport.add(fl.tabParent);
               }
 
               this.preActions();
 
               if (this.unloadTimer) {
@@ -4530,18 +4562,16 @@
               } else if (event.type == "TabRemotenessChange") {
                 this.onRemotenessChange(event.target);
               } else if (event.type == "sizemodechange") {
                 this.onSizeModeChange();
               } else if (event.type == "SwapDocShells") {
                 this.onSwapDocShells(event.originalTarget, event.detail);
               } else if (event.type == "EndSwapDocShells") {
                 this.onEndSwapDocShells(event.originalTarget, event.detail);
-              } else if (event.type == "MozTabChildNotReady") {
-                this.onTabChildNotReady(event.originalTarget);
               }
 
               this.postActions();
               this._processing = false;
             },
 
             /*
              * Telemetry and Profiler related helpers for recording tab switch
@@ -4576,41 +4606,22 @@
                 this.switchInProgress = false;
               }
             },
 
             spinnerDisplayed() {
               this.assert(!this.spinnerTab);
               let browser = this.requestedTab.linkedBrowser;
               this.assert(browser.isRemoteBrowser);
+              this.assert(browser.frameLoader.tabParent.hasPresented);
               TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
               // We have a second, similar probe for capturing recordings of
               // when the spinner is displayed for very long periods.
               TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
               this.addMarker("AsyncTabSwitch:SpinnerShown");
-
-              // What kind of tab is about to display this spinner? We have three basic
-              // kinds:
-              //
-              // 1) A tab that we've presented before
-              // 2) A tab that we've never presented before, and it's quite new
-              // 3) A tab that we've never presented before, but it's not so new
-              //
-              // Being "new" in this sense means being a tab that was created less than
-              // NEWNESS_THRESHOLD ms ago.
-
-              let histogram = Services.telemetry.getHistogramById("FX_TAB_SWITCH_SPINNER_TYPE");
-              if (browser.frameLoader.tabParent.hasPresented) {
-                // We've presented this tab before.
-                histogram.add("seen");
-              } else if (Date.now() - this.requestedTab.creationTime < this.NEWNESS_THRESHOLD) {
-                histogram.add("unseenNew");
-              } else {
-                histogram.add("unseenOld");
-              }
             },
 
             spinnerHidden() {
               this.assert(this.spinnerTab);
               this.log("DEBUG: spinner time = " +
                        TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window));
               TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
               TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
@@ -6010,16 +6021,17 @@
         ]]></body>
       </method>
 
       <method name="adjustTabstrip">
         <body><![CDATA[
           // If we're overflowing, tab widths don't change anymore, so we can
           // return early to avoid flushing layout.
           if (this.getAttribute("overflow") == "true") {
+            this.setAttribute("closebuttons", "activetab");
             return;
           }
 
           let numTabs = this.childNodes.length -
                         this.tabbrowser._removingTabs.length;
           if (numTabs > 2) {
             // This is an optimization to avoid layout flushes by calling
             // getBoundingClientRect() when we just opened a second tab. In
@@ -7177,20 +7189,16 @@
       </xul:stack>
     </content>
 
     <implementation>
       <constructor><![CDATA[
         if (!("_lastAccessed" in this)) {
           this.updateLastAccessed();
         }
-
-        if (!("_creationTime" in this)) {
-          this._creationTime = Date.now();
-        }
       ]]></constructor>
 
       <property name="_visuallySelected">
         <setter>
           <![CDATA[
           if (val)
             this.setAttribute("visuallyselected", "true");
           else
@@ -7285,22 +7293,16 @@
       </property>
       <method name="updateLastAccessed">
         <parameter name="aDate"/>
         <body><![CDATA[
           this._lastAccessed = this.selected ? Infinity : (aDate || Date.now());
         ]]></body>
       </method>
 
-      <property name="creationTime">
-        <getter>
-          return this._creationTime;
-        </getter>
-      </property>
-
       <field name="mOverCloseButton">false</field>
       <property name="_overPlayingIcon" readonly="true">
         <getter><![CDATA[
           let iconVisible = this.hasAttribute("soundplaying") ||
                             this.hasAttribute("muted") ||
                             this.hasAttribute("blocked");
           let soundPlayingIcon =
             document.getAnonymousElementByAttribute(this, "anonid", "soundplaying-icon");
--- a/browser/base/content/test/performance/browser_tabopen_reflows.js
+++ b/browser/base/content/test/performance/browser_tabopen_reflows.js
@@ -13,56 +13,34 @@
  * for tips on how to do that.
  */
 const EXPECTED_REFLOWS = [
   // selection change notification may cause querying the focused editor content
   // by IME and that will cause reflow.
   [
     "select@chrome://global/content/bindings/textbox.xml",
     "focusAndSelectUrlBar@chrome://browser/content/browser.js",
-    "openLinkIn@chrome://browser/content/utilityOverlay.js",
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
-    "BrowserOpenTab@chrome://browser/content/browser.js",
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
   ],
 
   // selection change notification may cause querying the focused editor content
   // by IME and that will cause reflow.
   [
     "select@chrome://global/content/bindings/textbox.xml",
     "focusAndSelectUrlBar@chrome://browser/content/browser.js",
-    "openLinkIn@chrome://browser/content/utilityOverlay.js",
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
-    "BrowserOpenTab@chrome://browser/content/browser.js",
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
   ],
 
   [
     "select@chrome://global/content/bindings/textbox.xml",
     "focusAndSelectUrlBar@chrome://browser/content/browser.js",
-    "openLinkIn@chrome://browser/content/utilityOverlay.js",
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
-    "BrowserOpenTab@chrome://browser/content/browser.js",
-  ],
-
-  [
-    "openLinkIn@chrome://browser/content/utilityOverlay.js",
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
-    "BrowserOpenTab@chrome://browser/content/browser.js",
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
   ],
 ];
 
-if (!gMultiProcessBrowser) {
-  EXPECTED_REFLOWS.push(
-    [
-      "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
-      "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml",
-      "onselect@chrome://browser/content/browser.xul",
-    ],
-  );
-}
-
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new tabs.
  */
 add_task(async function() {
   // If we've got a preloaded browser, get rid of it so that it
   // doesn't interfere with the test if it's loading. We have to
   // do this before we disable preloading or changing the new tab
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -4,18 +4,16 @@ support-files =
   test_bug1358314.html
 
 [browser_abandonment_telemetry.js]
 [browser_allow_process_switches_despite_related_browser.js]
 [browser_contextmenu_openlink_after_tabnavigated.js]
 [browser_tabCloseProbes.js]
 [browser_tabSpinnerProbe.js]
 skip-if = !e10s # Tab spinner is e10s only.
-[browser_tabSpinnerTypeProbe.js]
-skip-if = !e10s # Tab spinner is e10s only.
 [browser_tabSwitchPrintPreview.js]
 skip-if = os == 'mac'
 [browser_navigatePinnedTab.js]
 [browser_new_web_tab_in_file_process_pref.js]
 skip-if = !e10s # Pref and test only relevant for e10s.
 [browser_opened_file_tab_navigated_to_web.js]
 [browser_reload_deleted_file.js]
 [browser_tabswitch_updatecommands.js]
--- a/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
+++ b/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
@@ -1,89 +1,88 @@
 "use strict";
 
 /**
  * Tests the FX_TAB_SWITCH_SPINNER_VISIBLE_MS and
  * FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS telemetry probes
  */
-let gMinHangTime = 500; // ms
-let gMaxHangTime = 5 * 1000; // ms
-
-/**
- * Make a data URI for a generic webpage with a script that hangs for a given
- * amount of time.
- * @param  {?Number} aHangMs Number of milliseconds that the hang should last.
- *                   Defaults to 0.
- * @return {String}  The data URI generated.
- */
-function makeDataURI(aHangMs = 0) {
-  return `data:text/html,
-    <html>
-      <head>
-        <meta charset="utf-8"/>
-        <title>Tab Spinner Test</title>
-        <script>
-          function hang() {
-            let hangDuration = ${aHangMs};
-            if (hangDuration > 0) {
-              let startTime = window.performance.now();
-              while(window.performance.now() - startTime < hangDuration) {}
-            }
-          }
-        </script>
-      </head>
-      <body>
-        <h1 id='header'>Tab Spinner Test</h1>
-      </body>
-    </html>`;
-}
+const MIN_HANG_TIME = 500; // ms
+const MAX_HANG_TIME = 5 * 1000; // ms
 
 /**
  * Returns the sum of all values in an array.
  * @param  {Array}  aArray An array of integers
  * @return {Number} The sum of the integers in the array
  */
 function sum(aArray) {
   return aArray.reduce(function(previousValue, currentValue) {
     return previousValue + currentValue;
   });
 }
 
 /**
+ * Causes the content process for a remote <xul:browser> to run
+ * some busy JS for aMs milliseconds.
+ *
+ * @param {<xul:browser>} browser
+ *        The browser that's running in the content process that we're
+ *        going to hang.
+ * @param {int} aMs
+ *        The amount of time, in milliseconds, to hang the content process.
+ *
+ * @return {Promise}
+ *        Resolves once the hang is done.
+ */
+function hangContentProcess(browser, aMs) {
+  return ContentTask.spawn(browser, aMs, function*(ms) {
+    let then = Date.now();
+    while (Date.now() - then < ms) {
+      // Let's burn some CPU...
+    }
+  });
+}
+
+/**
  * A generator intended to be run as a Task. It tests one of the tab spinner
  * telemetry probes.
  * @param {String} aProbe The probe to test. Should be one of:
  *                  - FX_TAB_SWITCH_SPINNER_VISIBLE_MS
  *                  - FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS
  */
 async function testProbe(aProbe) {
   info(`Testing probe: ${aProbe}`);
   let histogram = Services.telemetry.getHistogramById(aProbe);
   let buckets = histogram.snapshot().ranges.filter(function(value) {
-    return (value > gMinHangTime && value < gMaxHangTime);
+    return (value > MIN_HANG_TIME && value < MAX_HANG_TIME);
   });
   let delayTime = buckets[0]; // Pick a bucket arbitrarily
 
   // The tab spinner does not show up instantly. We need to hang for a little
   // bit of extra time to account for the tab spinner delay.
   delayTime += gBrowser.selectedTab.linkedBrowser.getTabBrowser()._getSwitcher().TAB_SWITCH_TIMEOUT;
-  let dataURI1 = makeDataURI(delayTime);
-  let dataURI2 = makeDataURI();
 
-  let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI1);
+  // In order for a spinner to be shown, the tab must have presented before.
+  let origTab = gBrowser.selectedTab;
+  let hangTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  let hangBrowser = hangTab.linkedBrowser;
+  ok(hangBrowser.isRemoteBrowser, "New tab should be remote.");
+  ok(hangBrowser.frameLoader.tabParent.hasPresented, "New tab has presented.");
+
+  // Now switch back to the original tab and set up our hang.
+  await BrowserTestUtils.switchTab(gBrowser, origTab);
+
+  let tabHangPromise = hangContentProcess(hangBrowser, delayTime);
   histogram.clear();
-  // Queue a hang in the content process when the
-  // event loop breathes next.
-  ContentTask.spawn(tab1.linkedBrowser, null, async function() {
-    content.wrappedJSObject.hang();
-  });
-  let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI2);
+  let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, hangTab);
+  await tabHangPromise;
+  await hangTabSwitch;
+
+  // Now we should have a hang in our histogram.
   let snapshot = histogram.snapshot();
-  await BrowserTestUtils.removeTab(tab2);
-  await BrowserTestUtils.removeTab(tab1);
+  await BrowserTestUtils.removeTab(hangTab);
   ok(sum(snapshot.counts) > 0,
    `Spinner probe should now have a value in some bucket`);
 }
 
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({
     set: [
       ["dom.ipc.processCount", 1],
deleted file mode 100644
--- a/browser/base/content/test/tabs/browser_tabSpinnerTypeProbe.js
+++ /dev/null
@@ -1,187 +0,0 @@
-"use strict";
-
-// Keep this in sync with the order in Histograms.json for
-// FX_TAB_SWITCH_SPINNER_TYPE
-const CATEGORIES = [
-  "seen",
-  "unseenOld",
-  "unseenNew",
-];
-
-add_task(async function setup() {
-  await SpecialPowers.pushPrefEnv({
-    set: [
-      // We can interrupt JS to paint now, which is great for
-      // users, but bad for testing spinners. We temporarily
-      // disable that feature for this test so that we can
-      // easily get ourselves into a predictable tab spinner
-      // state.
-      ["browser.tabs.remote.force-paint", false],
-    ]
-  });
-});
-
-/**
- * Causes the content process for a remote <xul:browser> to run
- * some busy JS for aMs milliseconds.
- *
- * @param {<xul:browser>} browser
- *        The browser that's running in the content process that we're
- *        going to hang.
- * @param {int} aMs
- *        The amount of time, in milliseconds, to hang the content process.
- *
- * @return {Promise}
- *        Resolves once the hang is done.
- */
-function hangContentProcess(browser, aMs) {
-  return ContentTask.spawn(browser, aMs, async function(ms) {
-    let then = Date.now();
-    while (Date.now() - then < ms) {
-      // Let's burn some CPU...
-    }
-  });
-}
-
-/**
- * Takes a Telemetry histogram snapshot and makes sure
- * that the index for that value (as defined by CATEGORIES)
- * has a count of 1, and that it's the only value that
- * has been incremented.
- *
- * @param snapshot (Object)
- *        The Telemetry histogram snapshot to examine.
- * @param category (String)
- *        The category in CATEGORIES whose index we expect to have
- *        been set to 1.
- */
-function assertOnlyOneTypeSet(snapshot, category) {
-  let categoryIndex = CATEGORIES.indexOf(category);
-  Assert.equal(snapshot.counts[categoryIndex], 1,
-               `Should have seen the ${category} count increment.`);
-  // Use Array.prototype.reduce to sum up all of the
-  // snapshot.count entries
-  Assert.equal(snapshot.counts.reduce((a, b) => a + b), 1,
-               "Should only be 1 collected value.");
-}
-
-Assert.ok(gMultiProcessBrowser,
-  "These tests only makes sense in an e10s-enabled window.");
-
-let gHistogram = Services.telemetry
-                         .getHistogramById("FX_TAB_SWITCH_SPINNER_TYPE");
-
-/**
- * This test tests that the "seen" category for the FX_TAB_SWITCH_SPINNER_TYPE
- * probe works. This means that we show a spinner for a tab that we've
- * presented before.
- */
-add_task(async function test_seen_spinner_type_probe() {
-  let originalTab = gBrowser.selectedTab;
-
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: "http://example.com",
-  }, async function(browser) {
-    // We'll switch away from the current tab, then hang it, and then switch
-    // back to it. This should add to the "seen" type for the histogram.
-    let testTab = gBrowser.selectedTab;
-    await BrowserTestUtils.switchTab(gBrowser, originalTab);
-    gHistogram.clear();
-
-    let tabHangPromise = hangContentProcess(browser, 1000);
-    let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, testTab);
-    await tabHangPromise;
-    await hangTabSwitch;
-
-    // Okay, we should have gotten an entry in our Histogram for that one.
-    let snapshot = gHistogram.snapshot();
-    assertOnlyOneTypeSet(snapshot, "seen");
-    gHistogram.clear();
-  });
-});
-
-/**
- * This test tests that the "unseenOld" category for the FX_TAB_SWITCH_SPINNER_TYPE
- * probe works. This means that we show a spinner for a tab that we've never
- * seen before, and enough time has passed since its creation that we consider
- * it "old" (See the NEWNESS_THRESHOLD constant in the async tabswitcher for
- * the exact definition).
- */
-add_task(async function test_unseenOld_spinner_type_probe() {
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: "http://example.com",
-  }, async function(browser) {
-    const NEWNESS_THRESHOLD = gBrowser._getSwitcher().NEWNESS_THRESHOLD;
-
-    // First, create a new background tab, ensuring that it's in the same process
-    // as the current one.
-    let bgTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
-      sameProcessAsFrameLoader: browser.frameLoader,
-      inBackground: true,
-    });
-
-    await BrowserTestUtils.browserLoaded(bgTab.linkedBrowser);
-
-    // Now, let's fudge with the creationTime of the background tab so that
-    // it seems old. We'll also add a fudge-factor to the NEWNESS_THRESHOLD of 100ms
-    // to try to avoid any potential timing issues.
-    bgTab._creationTime = bgTab._creationTime - NEWNESS_THRESHOLD - 100;
-
-    // Okay, tab should seem sufficiently old now so that a spinner in it should
-    // qualify for "unseenOld". Let's hang it and switch to it.
-    gHistogram.clear();
-    let tabHangPromise = hangContentProcess(browser, 1000);
-    let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, bgTab);
-    await tabHangPromise;
-    await hangTabSwitch;
-
-    // Okay, we should have gotten an entry in our Histogram for that one.
-    let snapshot = gHistogram.snapshot();
-    assertOnlyOneTypeSet(snapshot, "unseenOld");
-
-    await BrowserTestUtils.removeTab(bgTab);
-  });
-});
-
-/**
- * This test tests that the "unseenNew" category for the FX_TAB_SWITCH_SPINNER_TYPE
- * probe works. This means that we show a spinner for a tab that we've never
- * seen before, and not enough time has passed since its creation that we consider
- * it "old" (See the NEWNESS_THRESHOLD constant in the async tabswitcher for
- * the exact definition).
- */
-add_task(async function test_unseenNew_spinner_type_probe() {
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: "http://example.com",
-  }, async function(browser) {
-    // First, create a new background tab, ensuring that it's in the same process
-    // as the current one.
-    let bgTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
-      sameProcessAsFrameLoader: browser.frameLoader,
-      inBackground: true,
-    });
-
-    await BrowserTestUtils.browserLoaded(bgTab.linkedBrowser);
-
-    // Now, let's fudge with the creationTime of the background tab so that
-    // it seems very new (created 1 minute into the future).
-    bgTab._creationTime = Date.now() + (60 * 1000);
-
-    // Okay, tab should seem sufficiently new now so that a spinner in it should
-    // qualify for "unseenNew". Let's hang it and switch to it.
-    gHistogram.clear();
-    let tabHangPromise = hangContentProcess(browser, 1000);
-    let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, bgTab);
-    await tabHangPromise;
-    await hangTabSwitch;
-
-    // Okay, we should have gotten an entry in our Histogram for that one.
-    let snapshot = gHistogram.snapshot();
-    assertOnlyOneTypeSet(snapshot, "unseenNew");
-
-    await BrowserTestUtils.removeTab(bgTab);
-  });
-});
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -391,16 +391,18 @@ function openLinkIn(url, where, params) 
     // 'where' is "tab" or "tabshifted", so we'll load the link in a new tab.
     loadInBackground = aInBackground;
     if (loadInBackground == null) {
       loadInBackground =
         aFromChrome ? false : getBoolPref("browser.tabs.loadInBackground");
     }
   }
 
+  let focusUrlBar = false;
+
   switch (where) {
   case "current":
     let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
 
     if (aAllowThirdPartyFixup) {
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
     }
@@ -432,30 +434,33 @@ function openLinkIn(url, where, params) 
       postData: aPostData,
       userContextId: aUserContextId
     });
     break;
   case "tabshifted":
     loadInBackground = !loadInBackground;
     // fall through
   case "tab":
+    focusUrlBar = !loadInBackground && w.isBlankPageURL(url);
+
     let tabUsedForLoad = w.gBrowser.loadOneTab(url, {
       referrerURI: aReferrerURI,
       referrerPolicy: aReferrerPolicy,
       charset: aCharset,
       postData: aPostData,
       inBackground: loadInBackground,
       allowThirdPartyFixup: aAllowThirdPartyFixup,
       relatedToCurrent: aRelatedToCurrent,
       skipAnimation: aSkipTabAnimation,
       allowMixedContent: aAllowMixedContent,
       noReferrer: aNoReferrer,
       userContextId: aUserContextId,
       originPrincipal: aPrincipal,
       triggeringPrincipal: aTriggeringPrincipal,
+      focusUrlBar,
     });
     targetBrowser = tabUsedForLoad.linkedBrowser;
 
     if (params.frameOuterWindowID != undefined && w) {
       // Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
       // event if it contains the expected frameOuterWindowID params.
       // (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
       // opening a new tab using the keyboard shortcut).
@@ -466,24 +471,20 @@ function openLinkIn(url, where, params) 
           sourceTabBrowser: w.gBrowser.selectedBrowser,
           sourceFrameOuterWindowID: params.frameOuterWindowID,
         },
       }, "webNavigation-createdNavigationTarget");
     }
     break;
   }
 
-  // Focus the content, but only if the browser used for the load is selected.
-  if (targetBrowser == w.gBrowser.selectedBrowser) {
+  if (!focusUrlBar && targetBrowser == w.gBrowser.selectedBrowser) {
+    // Focus the content, but only if the browser used for the load is selected.
     targetBrowser.focus();
   }
-
-  if (!loadInBackground && w.isBlankPageURL(url)) {
-    w.focusAndSelectUrlBar();
-  }
 }
 
 // Used as an onclick handler for UI elements with link-like behavior.
 // e.g. onclick="checkForMiddleClick(this, event);"
 function checkForMiddleClick(node, event) {
   // We should be using the disabled property here instead of the attribute,
   // but some elements that this function is used with don't support it (e.g.
   // menuitem).
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -401,21 +401,30 @@ this.PanelMultiView = class {
       let previousViewNode = aPreviousView || this._currentSubView;
       let playTransition = (!!previousViewNode && previousViewNode != viewNode);
 
       let dwu, previousRect;
       if (playTransition || this.panelViews) {
         dwu = this._dwu;
         previousRect = previousViewNode.__lastKnownBoundingRect =
           dwu.getBoundsWithoutFlushing(previousViewNode);
-        if (this.panelViews && !this._mainViewWidth) {
-          this._mainViewWidth = previousRect.width;
-          let top = dwu.getBoundsWithoutFlushing(previousViewNode.firstChild).top;
-          let bottom = dwu.getBoundsWithoutFlushing(previousViewNode.lastChild).bottom;
-          this._viewVerticalPadding = previousRect.height - (bottom - top);
+        if (this.panelViews) {
+          // Here go the measures that have the same caching lifetime as the width
+          // of the main view, i.e. 'forever', during the instance lifetime.
+          if (!this._mainViewWidth) {
+            this._mainViewWidth = previousRect.width;
+            let top = dwu.getBoundsWithoutFlushing(previousViewNode.firstChild).top;
+            let bottom = dwu.getBoundsWithoutFlushing(previousViewNode.lastChild).bottom;
+            this._viewVerticalPadding = previousRect.height - (bottom - top);
+          }
+          // Here go the measures that have the same caching lifetime as the height
+          // of the main view, i.e. whilst the panel is shown and/ or visible.
+          if (!this._mainViewHeight) {
+            this._mainViewHeight = previousRect.height;
+          }
         }
       }
 
       // Emit the ViewShowing event so that the widget definition has a chance
       // to lazily populate the subview with things.
       let detail = {
         blockers: new Set(),
         addBlocker(aPromise) {
@@ -486,17 +495,17 @@ this.PanelMultiView = class {
         // the panel's not even open.
         if (this._panel.state != "open") {
           onTransitionEnd();
           return;
         }
 
         if (aAnchor)
           aAnchor.setAttribute("open", true);
-        this._viewContainer.style.height = previousRect.height + "px";
+        this._viewContainer.style.height = Math.max(previousRect.height, this._mainViewHeight) + "px";
         this._viewContainer.style.width = previousRect.width + "px";
 
         this._transitioning = true;
         this._viewContainer.setAttribute("transition-reverse", reverse);
         let nodeToAnimate = reverse ? previousViewNode : viewNode;
 
         if (!reverse) {
           // We set the margin here to make sure the view is positioned next
@@ -533,67 +542,70 @@ this.PanelMultiView = class {
               viewRect.height = [viewNode.header, ...viewNode.children].reduce((acc, node) => {
                 return acc + dwu.getBoundsWithoutFlushing(node).height;
               }, this._viewVerticalPadding);
             }
           }
 
           // Set the viewContainer dimensions to make sure only the current view
           // is visible.
-          this._viewContainer.style.height = viewRect.height + "px";
+          this._viewContainer.style.height = Math.max(viewRect.height, this._mainViewHeight) + "px";
           this._viewContainer.style.width = viewRect.width + "px";
 
           // The 'magic' part: build up the amount of pixels to move right or left.
           let moveToLeft = (this._dir == "rtl" && !reverse) || (this._dir == "ltr" && reverse);
           let movementX = reverse ? viewRect.width : previousRect.width;
           let moveX = (moveToLeft ? "" : "-") + movementX;
           nodeToAnimate.style.transform = "translateX(" + moveX + "px)";
           // We're setting the width property to prevent flickering during the
           // sliding animation with smaller views.
           nodeToAnimate.style.width = viewRect.width + "px";
 
           let listener;
-          let seen = 0;
           this._viewContainer.addEventListener("transitionend", listener = ev => {
-            if (ev.target == this._viewContainer && ev.propertyName == "height") {
-              // Myeah, panel layout auto-resizing is a funky thing. We'll wait
-              // another few milliseconds to remove the width and height 'fixtures',
-              // to be sure we don't flicker annoyingly.
-              // NB: HACK! Bug 1363756 is there to fix this.
-              window.setTimeout(() => {
-                this._viewContainer.style.removeProperty("height");
-                this._viewContainer.style.removeProperty("width");
-              }, 500);
-              ++seen;
-            } else if (ev.target == nodeToAnimate && ev.propertyName == "transform") {
-              onTransitionEnd();
-              this._transitioning = false;
-              this._resetKeyNavigation(previousViewNode);
+            // It's quite common that `height` on the view container doesn't need
+            // to transition, so we make sure to do all the work on the transform
+            // transition-end, because that is guaranteed to happen.
+            if (ev.target != nodeToAnimate || ev.propertyName != "transform")
+              return;
+
+            this._viewContainer.removeEventListener("transitionend", listener);
+            onTransitionEnd();
+            this._transitioning = false;
+            this._resetKeyNavigation(previousViewNode);
 
-              // Take another breather, just like before, to wait for the 'current'
-              // attribute removal to take effect. This prevents a flicker.
-              // The cleanup we do doesn't affect the display anymore, so we're not
-              // too fussed about the timing here.
-              window.addEventListener("MozAfterPaint", () => {
-                nodeToAnimate.style.removeProperty("border-inline-start");
-                nodeToAnimate.style.removeProperty("transition");
-                nodeToAnimate.style.removeProperty("transform");
-                nodeToAnimate.style.removeProperty("width");
+            // Myeah, panel layout auto-resizing is a funky thing. We'll wait
+            // another few milliseconds to remove the width and height 'fixtures',
+            // to be sure we don't flicker annoyingly.
+            // NB: HACK! Bug 1363756 is there to fix this.
+            window.setTimeout(() => {
+              // Only remove the height when the view is larger than the main
+              // view, otherwise it'll snap back to its own height.
+              if (viewRect.height > this._mainViewHeight)
+                this._viewContainer.style.removeProperty("height");
+              this._viewContainer.style.removeProperty("width");
+            }, 500);
 
-                if (!reverse)
-                  viewNode.style.removeProperty("margin-inline-start");
-                if (aAnchor)
-                  aAnchor.removeAttribute("open");
+            // Take another breather, just like before, to wait for the 'current'
+            // attribute removal to take effect. This prevents a flicker.
+            // The cleanup we do doesn't affect the display anymore, so we're not
+            // too fussed about the timing here.
+            window.addEventListener("MozAfterPaint", () => {
+              nodeToAnimate.style.removeProperty("border-inline-start");
+              nodeToAnimate.style.removeProperty("transition");
+              nodeToAnimate.style.removeProperty("transform");
+              nodeToAnimate.style.removeProperty("width");
 
-                this._viewContainer.removeAttribute("transition-reverse");
-              }, { once: true });
-              ++seen;
-            }
-            if (seen == 2)
-              this._viewContainer.removeEventListener("transitionend", listener);
+              if (!reverse)
+                viewNode.style.removeProperty("margin-inline-start");
+              if (aAnchor)
+                aAnchor.removeAttribute("open");
+
+              this._viewContainer.removeAttribute("transition-reverse");
+            }, { once: true });
           });
         }, { once: true });
       } else if (!this.panelViews) {
         this._shiftMainView(aAnchor);
 
         this._mainViewHeight = this._viewStack.clientHeight;
 
         let newHeight = this._heightOfSubview(viewNode, this._subViews);
@@ -715,16 +727,17 @@ this.PanelMultiView = class {
         this._mainView.style.removeProperty("height");
         this.showMainView();
         if (!this.panelViews) {
           this._mainViewObserver.disconnect();
         } else {
           this.window.removeEventListener("keydown", this);
           this._panel.removeEventListener("mousemove", this);
           this._resetKeyNavigation();
+          this._mainViewHeight = 0;
         }
         break;
     }
   }
 
   /**
    * Allow for navigating subview buttons using the arrow keys and the Enter key.
    * The Up and Down keys can be used to navigate the list up and down and the
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -539,16 +539,32 @@
                        key="key_newNavigator"
                        command="cmd_newNavigator"/>
         <toolbarbutton id="appMenu-private-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newPrivateWindow.label;"
                        key="key_privatebrowsing"
                        command="Tools:PrivateBrowsing"/>
         <toolbarseparator/>
+        <toolbaritem id="appMenu-edit-controls" class="toolbaritem-combined-buttons" closemenu="none">
+          <label value="&editMenu.label;"/>
+          <toolbarbutton id="appMenu-cut-button"
+                         class="subviewbutton subviewbutton-iconic"
+                         command="cmd_cut"
+                         tooltip="dynamic-shortcut-tooltip"/>
+          <toolbarbutton id="appMenu-copy-button"
+                         class="subviewbutton subviewbutton-iconic"
+                         command="cmd_copy"
+                         tooltip="dynamic-shortcut-tooltip"/>
+          <toolbarbutton id="appMenu-paste-button"
+                         class="subviewbutton subviewbutton-iconic"
+                         command="cmd_paste"
+                         tooltip="dynamic-shortcut-tooltip"/>
+        </toolbaritem>
+        <toolbarseparator/>
         <toolbarbutton id="appMenu-open-file-button"
                        class="subviewbutton"
                        label="&openFileCmd.label;"
                        key="openFileKb"
                        command="Browser:OpenFile"
                        />
         <toolbarbutton id="appMenu-save-file-button"
                        class="subviewbutton"
--- a/browser/components/customizableui/test/browser_editcontrols_update.js
+++ b/browser/components/customizableui/test/browser_editcontrols_update.js
@@ -96,16 +96,21 @@ add_task(async function test_panelui_cus
   await startCustomizing();
   let navbar = document.getElementById("nav-bar").customizationTarget;
   simulateItemDrag(document.getElementById("edit-controls"), navbar);
   await endCustomizing();
 
   // updateEditUIVisibility should be called when customization ends but isn't. See bug 1359790.
   updateEditUIVisibility();
 
+  // The URL bar may have been focused to begin with, which means
+  // that subsequent calls to focus it won't result in command
+  // updates, so we'll make sure to blur it.
+  gURLBar.blur();
+
   let overridePromise = expectCommandUpdate(1);
   gURLBar.select();
   gURLBar.focus();
   gURLBar.value = "other";
   await overridePromise;
   checkState(false, "Update when edit-controls on toolbar and focused");
 
   overridePromise = expectCommandUpdate(1);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -578,31 +578,16 @@ BrowserGlue.prototype = {
 
     // apply distribution customizations
     // prefs are applied in _onAppDefaults()
     this._distributionCustomizer.applyCustomizations();
 
     // handle any UI migration
     this._migrateUI();
 
-    // This is support code for the location bar search suggestions; passing
-    // from opt-in to opt-out should respect the user's choice, thus we need
-    // to cache that choice in a pref for future use.
-    // Note: this is not in migrateUI because we need to uplift it. This
-    // code is also short-lived, since we can remove it as soon as opt-out
-    // search suggestions shipped in release (Bug 1344928).
-    try {
-      let urlbarPrefs = Services.prefs.getBranch("browser.urlbar.");
-      if (!urlbarPrefs.prefHasUserValue("searchSuggestionsChoice") &&
-          urlbarPrefs.getBoolPref("userMadeSearchSuggestionsChoice")) {
-        urlbarPrefs.setBoolPref("searchSuggestionsChoice",
-                                urlbarPrefs.getBoolPref("suggest.searches"));
-      }
-    } catch (ex) { /* missing any of the prefs is not critical */ }
-
     listeners.init();
 
     PageThumbs.init();
 
     DirectoryLinksProvider.init();
     NewTabUtils.init();
     NewTabUtils.links.addProvider(DirectoryLinksProvider);
     AboutNewTab.init();
@@ -1688,17 +1673,17 @@ BrowserGlue.prototype = {
         return;
       this._openPreferences("sync", { origin: "doorhanger" });
     }
     AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
   },
 
   // eslint-disable-next-line complexity
   _migrateUI: function BG__migrateUI() {
-    const UI_VERSION = 45;
+    const UI_VERSION = 46;
     const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
 
     let currentUIVersion;
     if (Services.prefs.prefHasUserValue("browser.migration.version")) {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } else {
       // This is a new profile, nothing to migrate.
       Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
@@ -1991,16 +1976,32 @@ BrowserGlue.prototype = {
       const LEGACY_PREF = "browser.shell.skipDefaultBrowserCheck";
       if (Services.prefs.prefHasUserValue(LEGACY_PREF)) {
         Services.prefs.setBoolPref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun",
                                    !Services.prefs.getBoolPref(LEGACY_PREF));
         Services.prefs.clearUserPref(LEGACY_PREF);
       }
     }
 
+    if (currentUIVersion < 46) {
+      // Search suggestions are now on by default.
+      // For privacy reasons, we want to respect previously made user's choice
+      // regarding the feature, so if it's known reflect that choice into the
+      // current pref.
+      // Note that in case of downgrade/upgrade we won't guarantee anything.
+      try {
+        Services.prefs.setBoolPref(
+          "browser.urlbar.suggest.searches",
+          Services.prefs.getBoolPref("browser.urlbar.searchSuggestionsChoice")
+        );
+      } catch (ex) {
+        // The pref is not set, nothing to do.
+      }
+    }
+
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
   // ------------------------------
   // public nsIBrowserGlue members
   // ------------------------------
 
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -300,22 +300,28 @@ var FormAutofillContent = {
 
   init() {
     FormAutofillUtils.defineLazyLogGetter(this, "FormAutofillContent");
 
     Services.cpmm.addMessageListener("FormAutofill:enabledStatus", this);
     Services.cpmm.addMessageListener("FormAutofill:savedFieldNames", this);
     Services.obs.addObserver(this, "earlyformsubmit");
 
-    if (Services.cpmm.initialProcessData.autofillEnabled) {
+    let autofillEnabled = Services.cpmm.initialProcessData.autofillEnabled;
+    if (autofillEnabled ||
+        // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
+        // autocomplete is registered before the focusin so register it in this case as long as the
+        // pref is true.
+        (autofillEnabled === undefined &&
+         Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled"))) {
       ProfileAutocomplete.ensureRegistered();
     }
 
     this.savedFieldNames =
-      Services.cpmm.initialProcessData.autofillSavedFieldNames || new Set();
+      Services.cpmm.initialProcessData.autofillSavedFieldNames;
   },
 
   _onFormSubmit(handler) {
     // TODO: Handle form submit event for profile saving(bug 990219) and metrics(bug 1341569).
   },
 
   notify(formElement) {
     this.log.debug("notified for form early submission");
@@ -395,16 +401,22 @@ var FormAutofillContent = {
 
   getAllFieldNames(element) {
     let formDetails = this.getFormDetails(element);
     return formDetails.map(record => record.fieldName);
   },
 
   identifyAutofillFields(doc) {
     this.log.debug("identifyAutofillFields:", "" + doc.location);
+
+    if (!this.savedFieldNames) {
+      this.log.debug("identifyAutofillFields: savedFieldNames are not known yet");
+      Services.cpmm.sendAsyncMessage("FormAutofill:InitStorage");
+    }
+
     let forms = [];
 
     // Collects root forms from inputs.
     for (let field of doc.getElementsByTagName("input")) {
       // We only consider text-like fields for now until we support radio and
       // checkbox buttons in the future.
       if (!field.mozIsTextField(true)) {
         continue;
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -34,58 +34,61 @@ this.EXPORTED_SYMBOLS = ["FormAutofillPa
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "profileStorage",
-                                  "resource://formautofill/ProfileStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillPreferences",
                                   "resource://formautofill/FormAutofillPreferences.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 const ENABLED_PREF = "extensions.formautofill.addresses.enabled";
 
 function FormAutofillParent() {
+  // Lazily load the storage JSM to avoid disk I/O until absolutely needed.
+  // Once storage is loaded we need to update saved field names and inform content processes.
+  XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
+    let {profileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
+    log.debug("Loading profileStorage");
+
+    profileStorage.initialize().then(function onStorageInitialized() {
+      // Update the saved field names to compute the status and update child processes.
+      this._updateSavedFieldNames();
+    }.bind(this));
+
+    return profileStorage;
+  });
 }
 
 FormAutofillParent.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
 
   /**
-   * Whether Form Autofill is enabled in preferences.
-   * Caches the latest value of this._getStatus().
+   * Cache of the Form Autofill status (considering preferences and storage).
    */
-  _enabled: false,
+  _active: null,
 
   /**
    * Initializes ProfileStorage and registers the message handler.
    */
   async init() {
-    log.debug("init");
-    await profileStorage.initialize();
-
     Services.obs.addObserver(this, "advanced-pane-loaded");
+    Services.ppmm.addMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.addMessageListener("FormAutofill:GetAddresses", this);
     Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
 
     // Observing the pref and storage changes
     Services.prefs.addObserver(ENABLED_PREF, this);
     Services.obs.addObserver(this, "formautofill-storage-changed");
-
-    // Force to trigger the onStatusChanged function for setting listeners properly
-    // while initizlization
-    this._setStatus(this._getStatus());
-    this._updateSavedFieldNames();
   },
 
   observe(subject, topic, data) {
     log.debug("observe:", topic, "with data:", data);
     switch (topic) {
       case "advanced-pane-loaded": {
         let useOldOrganization = Services.prefs.getBoolPref("browser.preferences.useOldOrganization",
                                                             false);
@@ -98,115 +101,114 @@ FormAutofillParent.prototype = {
         let insertBeforeNode = useOldOrganization ?
                                document.getElementById("locationBarGroup") :
                                document.getElementById("masterPasswordRow");
         parentNode.insertBefore(prefGroup, insertBeforeNode);
         break;
       }
 
       case "nsPref:changed": {
-        // Observe pref changes and update _enabled cache if status is changed.
-        let currentStatus = this._getStatus();
-        if (currentStatus !== this._enabled) {
-          this._setStatus(currentStatus);
-        }
+        // Observe pref changes and update _active cache if status is changed.
+        this._updateStatus();
         break;
       }
 
       case "formautofill-storage-changed": {
         // Early exit if the action is not "add" nor "remove"
         if (data != "add" && data != "remove") {
           break;
         }
 
         this._updateSavedFieldNames();
-        let currentStatus = this._getStatus();
-        if (currentStatus !== this._enabled) {
-          this._setStatus(currentStatus);
-        }
         break;
       }
 
       default: {
         throw new Error(`FormAutofillParent: Unexpected topic observed: ${topic}`);
       }
     }
   },
 
   /**
    * Broadcast the status to frames when the form autofill status changes.
    */
   _onStatusChanged() {
-    log.debug("_onStatusChanged: Status changed to", this._enabled);
-    Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._enabled);
+    log.debug("_onStatusChanged: Status changed to", this._active);
+    Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._active);
     // Sync process data autofillEnabled to make sure the value up to date
     // no matter when the new content process is initialized.
-    Services.ppmm.initialProcessData.autofillEnabled = this._enabled;
+    Services.ppmm.initialProcessData.autofillEnabled = this._active;
   },
 
   /**
-   * Query pref and storage status to determine the overall status for
+   * Query preference and storage status to determine the overall status of the
    * form autofill feature.
    *
-   * @returns {boolean} status of form autofill feature
+   * @returns {boolean} whether form autofill is active (enabled and has data)
    */
-  _getStatus() {
+  _computeStatus() {
     if (!Services.prefs.getBoolPref(ENABLED_PREF)) {
       return false;
     }
 
-    return profileStorage.addresses.getAll({noComputedFields: true}).length > 0;
+    return Services.ppmm.initialProcessData.autofillSavedFieldNames.size > 0;
   },
 
   /**
-   * Set status and trigger _onStatusChanged.
-   *
-   * @param {boolean} newStatus The latest status we want to set for _enabled
+   * Update the status and trigger _onStatusChanged, if necessary.
    */
-  _setStatus(newStatus) {
-    this._enabled = newStatus;
-    this._onStatusChanged();
+  _updateStatus() {
+    let wasActive = this._active;
+    this._active = this._computeStatus();
+    if (this._active !== wasActive) {
+      this._onStatusChanged();
+    }
   },
 
   /**
    * Handles the message coming from FormAutofillContent.
    *
    * @param   {string} message.name The name of the message.
    * @param   {object} message.data The data of the message.
    * @param   {nsIFrameMessageManager} message.target Caller's message manager.
    */
   receiveMessage({name, data, target}) {
     switch (name) {
+      case "FormAutofill:InitStorage": {
+        this.profileStorage.initialize();
+        break;
+      }
       case "FormAutofill:GetAddresses": {
         this._getAddresses(data, target);
         break;
       }
       case "FormAutofill:SaveAddress": {
         if (data.guid) {
-          profileStorage.addresses.update(data.guid, data.address);
+          this.profileStorage.addresses.update(data.guid, data.address);
         } else {
-          profileStorage.addresses.add(data.address);
+          this.profileStorage.addresses.add(data.address);
         }
         break;
       }
       case "FormAutofill:RemoveAddresses": {
-        data.guids.forEach(guid => profileStorage.addresses.remove(guid));
+        data.guids.forEach(guid => this.profileStorage.addresses.remove(guid));
         break;
       }
     }
   },
 
   /**
    * Uninitializes FormAutofillParent. This is for testing only.
    *
    * @private
    */
   _uninit() {
-    profileStorage._saveImmediately();
+    this.profileStorage._saveImmediately();
 
+    Services.ppmm.removeMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.removeMessageListener("FormAutofill:GetAddresses", this);
     Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
     Services.obs.removeObserver(this, "advanced-pane-loaded");
     Services.prefs.removeObserver(ENABLED_PREF, this);
   },
 
   /**
@@ -220,41 +222,43 @@ FormAutofillParent.prototype = {
    *         The input autocomplete property's information.
    * @param  {nsIFrameMessageManager} target
    *         Content's message manager.
    */
   _getAddresses({searchString, info}, target) {
     let addresses = [];
 
     if (info && info.fieldName) {
-      addresses = profileStorage.addresses.getByFilter({searchString, info});
+      addresses = this.profileStorage.addresses.getByFilter({searchString, info});
     } else {
-      addresses = profileStorage.addresses.getAll();
+      addresses = this.profileStorage.addresses.getAll();
     }
 
     target.sendAsyncMessage("FormAutofill:Addresses", addresses);
   },
 
   _updateSavedFieldNames() {
+    log.debug("_updateSavedFieldNames");
     if (!Services.ppmm.initialProcessData.autofillSavedFieldNames) {
       Services.ppmm.initialProcessData.autofillSavedFieldNames = new Set();
     } else {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
     }
 
-    profileStorage.addresses.getAll().forEach((address) => {
+    this.profileStorage.addresses.getAll().forEach((address) => {
       Object.keys(address).forEach((fieldName) => {
         if (!address[fieldName]) {
           return;
         }
         Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
       });
     });
 
     // Remove the internal guid and metadata fields.
-    profileStorage.INTERNAL_FIELDS.forEach((fieldName) => {
+    this.profileStorage.INTERNAL_FIELDS.forEach((fieldName) => {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.delete(fieldName);
     });
 
     Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
                                         Services.ppmm.initialProcessData.autofillSavedFieldNames);
+    this._updateStatus();
   },
 };
--- a/browser/extensions/formautofill/bootstrap.js
+++ b/browser/extensions/formautofill/bootstrap.js
@@ -25,55 +25,46 @@ function insertStyleSheet(domWindow, url
 
   if (CACHED_STYLESHEETS.has(domWindow)) {
     CACHED_STYLESHEETS.get(domWindow).push(styleSheet);
   } else {
     CACHED_STYLESHEETS.set(domWindow, [styleSheet]);
   }
 }
 
-let windowListener = {
-  onOpenWindow(window) {
-    let domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+function onMaybeOpenPopup(evt) {
+  let domWindow = evt.target.ownerGlobal;
+  if (CACHED_STYLESHEETS.has(domWindow)) {
+    // This window already has autofill stylesheets.
+    return;
+  }
 
-    domWindow.addEventListener("load", function onWindowLoaded() {
-      insertStyleSheet(domWindow, STYLESHEET_URI);
-    }, {once: true});
-  },
-};
+  insertStyleSheet(domWindow, STYLESHEET_URI);
+}
 
 function startup() {
   if (!Services.prefs.getBoolPref("extensions.formautofill.experimental")) {
     return;
   }
 
+  // Listen for the autocomplete popup message to lazily append our stylesheet related to the popup.
+  Services.mm.addMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
+
   let parent = new FormAutofillParent();
-  let enumerator = Services.wm.getEnumerator("navigator:browser");
-  // Load stylesheet to already opened windows
-  while (enumerator.hasMoreElements()) {
-    let win = enumerator.getNext();
-    let domWindow = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-
-    insertStyleSheet(domWindow, STYLESHEET_URI);
-  }
-
-  Services.wm.addListener(windowListener);
-
   parent.init().catch(Cu.reportError);
   Services.ppmm.loadProcessScript("data:,new " + function() {
     Components.utils.import("resource://formautofill/FormAutofillContent.jsm");
   }, true);
   Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true);
 }
 
 function shutdown() {
-  Services.wm.removeListener(windowListener);
+  Services.mm.removeMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
 
   let enumerator = Services.wm.getEnumerator("navigator:browser");
-
   while (enumerator.hasMoreElements()) {
     let win = enumerator.getNext();
     let domWindow = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
     let cachedStyleSheets = CACHED_STYLESHEETS.get(domWindow);
 
     if (!cachedStyleSheets) {
       continue;
     }
rename from browser/extensions/formautofill/test/unit/test_enabledStatus.js
rename to browser/extensions/formautofill/test/unit/test_activeStatus.js
--- a/browser/extensions/formautofill/test/unit/test_enabledStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -2,101 +2,94 @@
  * Test for status handling in Form Autofill Parent.
  */
 
 "use strict";
 
 Cu.import("resource://formautofill/FormAutofillParent.jsm");
 Cu.import("resource://formautofill/ProfileStorage.jsm");
 
-add_task(async function test_enabledStatus_init() {
+add_task(async function test_activeStatus_init() {
   let formAutofillParent = new FormAutofillParent();
-  sinon.spy(formAutofillParent, "_setStatus");
+  sinon.spy(formAutofillParent, "_updateStatus");
 
-  // Default status is false before initialization
-  do_check_eq(formAutofillParent._enabled, false);
+  // Default status is null before initialization
+  do_check_eq(formAutofillParent._active, null);
   do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, undefined);
 
   await formAutofillParent.init();
-  do_check_eq(formAutofillParent._setStatus.called, true);
+  // init shouldn't call updateStatus since that requires storage which will
+  // lead to startup time regressions.
+  do_check_eq(formAutofillParent._updateStatus.called, false);
+  do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, undefined);
+
+  // Initialize profile storage
+  await formAutofillParent.profileStorage.initialize();
+  // Upon first initializing profile storage, status should be computed.
+  do_check_eq(formAutofillParent._updateStatus.called, true);
   do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, false);
 
   formAutofillParent._uninit();
 });
 
-add_task(function* test_enabledStatus_observe() {
+add_task(async function test_activeStatus_observe() {
   let formAutofillParent = new FormAutofillParent();
-  sinon.stub(formAutofillParent, "_getStatus");
-  sinon.spy(formAutofillParent, "_setStatus");
-  sinon.stub(formAutofillParent, "_updateSavedFieldNames");
+  sinon.stub(formAutofillParent, "_computeStatus");
+  sinon.spy(formAutofillParent, "_onStatusChanged");
 
-  // _enabled = _getStatus() => No need to trigger onStatusChanged
-  formAutofillParent._enabled = true;
-  formAutofillParent._getStatus.returns(true);
+  // _active = _computeStatus() => No need to trigger _onStatusChanged
+  formAutofillParent._active = true;
+  formAutofillParent._computeStatus.returns(true);
   formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.addresses.enabled");
-  do_check_eq(formAutofillParent._setStatus.called, false);
+  do_check_eq(formAutofillParent._onStatusChanged.called, false);
 
-  // _enabled != _getStatus() => Need to trigger onStatusChanged
-  formAutofillParent._getStatus.returns(false);
+  // _active != _computeStatus() => Need to trigger _onStatusChanged
+  formAutofillParent._computeStatus.returns(false);
+  formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.addresses.enabled");
-  do_check_eq(formAutofillParent._setStatus.called, true);
+  do_check_eq(formAutofillParent._onStatusChanged.called, true);
 
-  // profile added => Need to trigger onStatusChanged
-  formAutofillParent._getStatus.returns(!formAutofillParent._enabled);
-  formAutofillParent._setStatus.reset();
+  // profile added => Need to trigger _onStatusChanged
+  formAutofillParent._computeStatus.returns(!formAutofillParent._active);
+  formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "formautofill-storage-changed", "add");
-  do_check_eq(formAutofillParent._setStatus.called, true);
+  do_check_eq(formAutofillParent._onStatusChanged.called, true);
 
-  // profile removed => Need to trigger onStatusChanged
-  formAutofillParent._getStatus.returns(!formAutofillParent._enabled);
-  formAutofillParent._setStatus.reset();
+  // profile removed => Need to trigger _onStatusChanged
+  formAutofillParent._computeStatus.returns(!formAutofillParent._active);
+  formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "formautofill-storage-changed", "remove");
-  do_check_eq(formAutofillParent._setStatus.called, true);
+  do_check_eq(formAutofillParent._onStatusChanged.called, true);
 
-  // profile updated => no need to trigger onStatusChanged
-  formAutofillParent._getStatus.returns(!formAutofillParent._enabled);
-  formAutofillParent._setStatus.reset();
+  // profile updated => no need to trigger _onStatusChanged
+  formAutofillParent._computeStatus.returns(!formAutofillParent._active);
+  formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "formautofill-storage-changed", "update");
-  do_check_eq(formAutofillParent._setStatus.called, false);
+  do_check_eq(formAutofillParent._onStatusChanged.called, false);
 });
 
-add_task(function* test_enabledStatus_getStatus() {
+add_task(async function test_activeStatus_computeStatus() {
   let formAutofillParent = new FormAutofillParent();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   });
 
   sinon.stub(profileStorage.addresses, "getAll");
   profileStorage.addresses.getAll.returns([]);
 
   // pref is enabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
-  do_check_eq(formAutofillParent._getStatus(), false);
+  do_check_eq(formAutofillParent._computeStatus(), false);
 
   // pref is disabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
-  do_check_eq(formAutofillParent._getStatus(), false);
+  do_check_eq(formAutofillParent._computeStatus(), false);
 
-  profileStorage.addresses.getAll.returns(["test-profile"]);
+  profileStorage.addresses.getAll.returns([{"given-name": "John"}]);
+  formAutofillParent.observe(null, "formautofill-storage-changed", "add");
   // pref is enabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
-  do_check_eq(formAutofillParent._getStatus(), true);
+  do_check_eq(formAutofillParent._computeStatus(), true);
 
   // pref is disabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
-  do_check_eq(formAutofillParent._getStatus(), false);
+  do_check_eq(formAutofillParent._computeStatus(), false);
 });
-
-add_task(function* test_enabledStatus_setStatus() {
-  let formAutofillParent = new FormAutofillParent();
-  sinon.spy(formAutofillParent, "_onStatusChanged");
-
-  formAutofillParent._setStatus(true);
-  do_check_eq(formAutofillParent._enabled, true);
-  do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, true);
-  do_check_eq(formAutofillParent._onStatusChanged.called, true);
-
-  formAutofillParent._onStatusChanged.reset();
-  formAutofillParent._setStatus(false);
-  do_check_eq(formAutofillParent._enabled, false);
-  do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, false);
-  do_check_eq(formAutofillParent._onStatusChanged.called, true);
-});
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -7,16 +7,17 @@
 Cu.import("resource://formautofill/FormAutofillParent.jsm");
 Cu.import("resource://formautofill/ProfileStorage.jsm");
 
 add_task(async function test_profileSavedFieldNames_init() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
 
   await formAutofillParent.init();
+  await formAutofillParent.profileStorage.initialize();
   do_check_eq(formAutofillParent._updateSavedFieldNames.called, true);
 
   formAutofillParent._uninit();
 });
 
 add_task(async function test_profileSavedFieldNames_observe() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -11,21 +11,21 @@ support-files =
 [heuristics/third_party/test_HomeDepot.js]
 [heuristics/third_party/test_Macys.js]
 [heuristics/third_party/test_NewEgg.js]
 [heuristics/third_party/test_OfficeDepot.js]
 [heuristics/third_party/test_QVC.js]
 [heuristics/third_party/test_Sears.js]
 [heuristics/third_party/test_Staples.js]
 [heuristics/third_party/test_Walmart.js]
+[test_activeStatus.js]
 [test_addressRecords.js]
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
 [test_creditCardRecords.js]
-[test_enabledStatus.js]
 [test_findLabelElements.js]
 [test_getFormInputDetails.js]
 [test_isCJKName.js]
 [test_markAsAutofillField.js]
 [test_nameUtils.js]
 [test_onFormSubmitted.js]
 [test_profileAutocompleteResult.js]
 [test_savedFieldNames.js]
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -483,16 +483,25 @@ editBookmark.removeBookmarks.label=Remov
 # Post Update Notifications
 pu.notifyButton.label=Details…
 pu.notifyButton.accesskey=D
 # LOCALIZATION NOTE %S will be replaced by the short name of the application.
 puNotifyText=%S has been updated
 puAlertTitle=%S Updated
 puAlertText=Click here for details
 
+# Application menu
+
+# LOCALIZATION NOTE (cut-button.tooltip): %S is the keyboard shortcut.
+cut-button.tooltip = Cut (%S)
+# LOCALIZATION NOTE (copy-button.tooltip): %S is the keyboard shortcut.
+copy-button.tooltip = Copy (%S)
+# LOCALIZATION NOTE (paste-button.tooltip): %S is the keyboard shortcut.
+paste-button.tooltip = Paste (%S)
+
 # Geolocation UI
 
 geolocation.allowLocation=Allow Location Access
 geolocation.allowLocation.accesskey=A
 geolocation.dontAllowLocation=Don’t Allow
 geolocation.dontAllowLocation.accesskey=n
 geolocation.shareWithSite3=Will you allow %S to access your location?
 geolocation.shareWithFile3=Will you allow this local file to access your location?
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -1291,16 +1291,42 @@ photonpanelmultiview .subviewbutton[chec
   -moz-context-properties: fill;
 }
 
 photonpanelmultiview .subviewbutton > .menu-iconic-left {
   -moz-appearance: none;
   margin-inline-end: 0;
 }
 
+photonpanelmultiview .toolbaritem-combined-buttons:-moz-any(:not([cui-areatype="toolbar"]), [overflowedItem="true"]) {
+  -moz-box-align: center;
+  -moz-box-orient: horizontal;
+  border: 0;
+  margin-inline-end: 8px;
+}
+
+photonpanelmultiview .toolbaritem-combined-buttons > label {
+  -moz-box-flex: 1;
+  font: menu;
+  margin: 0;
+  padding-inline-start: 36px; /* 12px toolbarbutton padding + 16px icon + 8px label padding start */
+}
+
+photonpanelmultiview .PanelUI-subView .toolbaritem-combined-buttons > .subviewbutton {
+  -moz-box-flex: 0;
+  height: auto;
+  margin-inline-start: 18px;
+  min-width: auto;
+  padding: 4px;
+}
+
+photonpanelmultiview .PanelUI-subView .toolbaritem-combined-buttons > .subviewbutton > .toolbarbutton-text {
+  display: none;
+}
+
 /* END photon adjustments */
 
 panelview .toolbarbutton-1,
 .widget-overflow-list > .toolbarbutton-1:not(:first-child),
 .widget-overflow-list > toolbaritem:not(:first-child) {
   margin-top: 6px;
 }
 
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -93,17 +93,19 @@
   skin/classic/browser/fxa/android.png                         (../shared/fxa/android.png)
   skin/classic/browser/fxa/android@2x.png                      (../shared/fxa/android@2x.png)
   skin/classic/browser/fxa/ios.png                             (../shared/fxa/ios.png)
   skin/classic/browser/fxa/ios@2x.png                          (../shared/fxa/ios@2x.png)
 
 
   skin/classic/browser/addons.svg                     (../shared/icons/addons.svg)
   skin/classic/browser/back.svg                       (../shared/icons/back.svg)
+#ifndef MOZ_PHOTON_THEME
   skin/classic/browser/back-large.svg                 (../shared/icons/back-large.svg)
+#endif
   skin/classic/browser/bookmark.svg                   (../shared/icons/bookmark.svg)
   skin/classic/browser/bookmark-hollow.svg            (../shared/icons/bookmark-hollow.svg)
   skin/classic/browser/bookmarksMenu.svg              (../shared/icons/bookmarksMenu.svg)
   skin/classic/browser/characterEncoding.svg          (../shared/icons/characterEncoding.svg)
   skin/classic/browser/chevron.svg                    (../shared/icons/chevron.svg)
   skin/classic/browser/containers.svg                       (../shared/icons/containers.svg)
   skin/classic/browser/developer.svg                  (../shared/icons/developer.svg)
   skin/classic/browser/download.svg                   (../shared/icons/download.svg)
--- a/browser/themes/shared/menupanel.inc.css
+++ b/browser/themes/shared/menupanel.inc.css
@@ -221,8 +221,23 @@ toolbarpaletteitem[place="palette"] > #z
   list-style-image: url(chrome://browser/skin/menu-icons/find.svg);
   -moz-context-properties: fill;
 }
 
 #appMenu-help-button {
   list-style-image: url(chrome://browser/skin/menu-icons/help.svg);
   -moz-context-properties: fill;
 }
+
+#appMenu-cut-button {
+  list-style-image: url(chrome://browser/skin/edit-cut.svg);
+  -moz-context-properties: fill;
+}
+
+#appMenu-copy-button {
+  list-style-image: url(chrome://browser/skin/edit-copy.svg);
+  -moz-context-properties: fill;
+}
+
+#appMenu-paste-button {
+  list-style-image: url(chrome://browser/skin/edit-paste.svg);
+  -moz-context-properties: fill;
+}
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -19,17 +19,21 @@ toolbar[brighttext] :-moz-any(@primaryTo
 #reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 %endif
 #nav-bar-overflow-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
 #back-button {
+%ifdef MOZ_PHOTON_THEME
+  list-style-image: url("chrome://browser/skin/back.svg");
+%else
   list-style-image: url("chrome://browser/skin/back-large.svg");
+%endif
 }
 
 #forward-button {
   list-style-image: url("chrome://browser/skin/forward.svg");
 }
 
 %ifdef MOZ_PHOTON_THEME
 #reload-button {
--- a/devtools/client/aboutdebugging/components/addons/target.js
+++ b/devtools/client/aboutdebugging/components/addons/target.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
 const { createClass, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
-const { debugAddon } = require("../../modules/addon");
+const { debugAddon, uninstallAddon } = require("../../modules/addon");
 const Services = require("Services");
 
 loader.lazyImporter(this, "BrowserToolboxProcess",
   "resource://devtools/client/framework/ToolboxProcess.jsm");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/main", true);
 
@@ -83,33 +83,36 @@ module.exports = createClass({
     }).isRequired
   },
 
   debug() {
     let { target } = this.props;
     debugAddon(target.addonID);
   },
 
+  uninstall() {
+    let { target } = this.props;
+    uninstallAddon(target.addonID);
+  },
+
   reload() {
     let { client, target } = this.props;
     // This function sometimes returns a partial promise that only
     // implements then().
     client.request({
       to: target.addonActor,
       type: "reload"
     }).then(() => {}, error => {
       throw new Error(
         "Error reloading addon " + target.addonID + ": " + error);
     });
   },
 
   render() {
     let { target, debugDisabled } = this.props;
-    // Only temporarily installed add-ons can be reloaded.
-    const canBeReloaded = target.temporarilyInstalled;
 
     return dom.li(
       { className: "addon-target-container", "data-addon-id": target.addonID },
       dom.div({ className: "target" },
         dom.img({
           className: "target-icon",
           role: "presentation",
           src: target.icon
@@ -122,19 +125,24 @@ module.exports = createClass({
         ...internalIDForTarget(target),
       ),
       dom.div({className: "addon-target-actions"},
         dom.button({
           className: "debug-button addon-target-button",
           onClick: this.debug,
           disabled: debugDisabled,
         }, Strings.GetStringFromName("debug")),
-        dom.button({
-          className: "reload-button addon-target-button",
-          onClick: this.reload,
-          disabled: !canBeReloaded,
-          title: !canBeReloaded ?
-            Strings.GetStringFromName("reloadDisabledTooltip") : ""
-        }, Strings.GetStringFromName("reload"))
+        target.temporarilyInstalled
+          ? dom.button({
+            className: "reload-button addon-target-button",
+            onClick: this.reload,
+          }, Strings.GetStringFromName("reload"))
+          : null,
+        target.temporarilyInstalled
+          ? dom.button({
+            className: "uninstall-button addon-target-button",
+            onClick: this.uninstall,
+          }, Strings.GetStringFromName("remove"))
+          : null,
       ),
     );
   }
 });
--- a/devtools/client/aboutdebugging/modules/addon.js
+++ b/devtools/client/aboutdebugging/modules/addon.js
@@ -1,23 +1,29 @@
 /* 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";
 
 loader.lazyImporter(this, "BrowserToolboxProcess",
   "resource://devtools/client/framework/ToolboxProcess.jsm");
+loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 
 let toolbox = null;
 
 exports.debugAddon = function (addonID) {
   if (toolbox) {
     toolbox.close();
   }
 
   toolbox = BrowserToolboxProcess.init({
     addonID,
     onClose: () => {
       toolbox = null;
     }
   });
 };
+
+exports.uninstallAddon = async function (addonID) {
+  let addon = await AddonManager.getAddonByID(addonID);
+  return addon && addon.uninstall();
+};
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -27,16 +27,17 @@ tags = webextensions
 tags = webextensions
 [browser_addons_debug_webextension_nobg.js]
 tags = webextensions
 [browser_addons_debug_webextension_popup.js]
 tags = webextensions
 [browser_addons_debugging_initial_state.js]
 [browser_addons_install.js]
 [browser_addons_reload.js]
+[browser_addons_remove.js]
 [browser_addons_toggle_debug.js]
 [browser_page_not_found.js]
 [browser_service_workers.js]
 [browser_service_workers_fetch_flag.js]
 [browser_service_workers_multi_content_process.js]
 skip-if = !e10s # This test is only valid in e10s
 [browser_service_workers_not_compatible.js]
 [browser_service_workers_push.js]
--- a/devtools/client/aboutdebugging/test/browser_addons_reload.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -1,74 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const ADDON_ID = "test-devtools@mozilla.org";
 const ADDON_NAME = "test-devtools";
 
-/**
- * Returns a promise that resolves when the given add-on event is fired. The
- * resolved value is an array of arguments passed for the event.
- */
-function promiseAddonEvent(event) {
-  return new Promise(resolve => {
-    let listener = {
-      [event]: function (...args) {
-        AddonManager.removeAddonListener(listener);
-        resolve(args);
-      }
-    };
-
-    AddonManager.addAddonListener(listener);
-  });
-}
-
-function* tearDownAddon(addon) {
-  const onUninstalled = promiseAddonEvent("onUninstalled");
-  addon.uninstall();
-  const [uninstalledAddon] = yield onUninstalled;
-  is(uninstalledAddon.id, addon.id,
-     `Add-on was uninstalled: ${uninstalledAddon.id}`);
-}
-
 function getReloadButton(document, addonName) {
   const names = getInstalledAddonNames(document);
   const name = names.filter(element => element.textContent === addonName)[0];
   ok(name, `Found ${addonName} add-on in the list`);
   const targetElement = name.parentNode.parentNode;
   const reloadButton = targetElement.querySelector(".reload-button");
   info(`Found reload button for ${addonName}`);
   return reloadButton;
 }
 
-function installAddonWithManager(filePath) {
-  return new Promise((resolve, reject) => {
-    AddonManager.getInstallForFile(filePath, install => {
-      if (!install) {
-        throw new Error(`An install was not created for ${filePath}`);
-      }
-      install.addListener({
-        onDownloadFailed: reject,
-        onDownloadCancelled: reject,
-        onInstallFailed: reject,
-        onInstallCancelled: reject,
-        onInstallEnded: resolve
-      });
-      install.install();
-    });
-  });
-}
-
-function getAddonByID(addonId) {
-  return new Promise(resolve => {
-    AddonManager.getAddonByID(addonId, addon => resolve(addon));
-  });
-}
-
 /**
  * Creates a web extension from scratch in a temporary location.
  * The object must be removed when you're finished working with it.
  */
 class TempWebExt {
   constructor(addonId) {
     this.addonId = addonId;
     this.tmpDir = FileUtils.getDir("TmpD", ["browser_addons_reload"]);
@@ -111,17 +62,16 @@ add_task(function* reloadButtonReloadsAd
   yield waitForInitialAddonList(document);
   yield installAddon({
     document,
     path: "addons/unpacked/install.rdf",
     name: ADDON_NAME,
   });
 
   const reloadButton = getReloadButton(document, ADDON_NAME);
-  is(reloadButton.disabled, false, "Reload button should not be disabled");
   is(reloadButton.title, "", "Reload button should not have a tooltip");
   const onInstalled = promiseAddonEvent("onInstalled");
 
   const onBootstrapInstallCalled = new Promise(done => {
     Services.obs.addObserver(function listener() {
       Services.obs.removeObserver(listener, ADDON_NAME);
       info("Add-on was re-installed: " + ADDON_NAME);
       done();
@@ -193,15 +143,13 @@ add_task(function* onlyTempInstalledAddo
   yield waitForInitialAddonList(document);
   const onAddonListUpdated = waitForMutation(getAddonList(document),
                                              { childList: true });
   yield installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
   yield onAddonListUpdated;
   const addon = yield getAddonByID("bug1273184@tests");
 
   const reloadButton = getReloadButton(document, addon.name);
-  ok(reloadButton, "Reload button exists");
-  is(reloadButton.disabled, true, "Reload button should be disabled");
-  ok(reloadButton.title, "Disabled reload button should have a tooltip");
+  ok(!reloadButton, "There should not be a reload button");
 
   yield tearDownAddon(addon);
   yield closeAboutDebugging(tab);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_remove.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+function getTargetEl(document, id) {
+  return document.querySelector(`[data-addon-id="${id}"]`);
+}
+
+function getRemoveButton(document, id) {
+  return document.querySelector(`[data-addon-id="${id}"] .uninstall-button`);
+}
+
+add_task(function* removeLegacyExtension() {
+  const addonID = "test-devtools@mozilla.org";
+  const addonName = "test-devtools";
+
+  const { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+
+  // Install this add-on, and verify that it appears in the about:debugging UI
+  yield installAddon({
+    document,
+    path: "addons/unpacked/install.rdf",
+    name: addonName,
+  });
+
+  ok(getTargetEl(document, addonID), "add-on is shown");
+
+  // Click the remove button and wait for the DOM to change.
+  const addonListMutation = waitForMutation(
+    getTemporaryAddonList(document),
+    { childList: true });
+  getRemoveButton(document, addonID).click();
+  yield addonListMutation;
+
+  ok(!getTargetEl(document, addonID), "add-on is not shown");
+
+  yield closeAboutDebugging(tab);
+});
+
+add_task(function* removeWebextension() {
+  const addonID = "test-devtools-webextension@mozilla.org";
+  const addonName = "test-devtools-webextension";
+
+  const { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+
+  // Install this add-on, and verify that it appears in the about:debugging UI
+  yield installAddon({
+    document,
+    path: "addons/test-devtools-webextension/manifest.json",
+    name: addonName,
+    isWebExtension: true,
+  });
+
+  ok(getTargetEl(document, addonID), "add-on is shown");
+
+  // Click the remove button and wait for the DOM to change.
+  const addonListMutation = waitForMutation(
+    getTemporaryAddonList(document),
+    { childList: true });
+  getRemoveButton(document, addonID).click();
+  yield addonListMutation;
+
+  ok(!getTargetEl(document, addonID), "add-on is not shown");
+
+  yield closeAboutDebugging(tab);
+});
+
+add_task(function* onlyTempInstalledAddonsCanBeRemoved() {
+  const { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+  const onAddonListUpdated = waitForMutation(getAddonList(document),
+                                             { childList: true });
+  yield installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
+  yield onAddonListUpdated;
+  const addon = yield getAddonByID("bug1273184@tests");
+
+  const removeButton = getRemoveButton(document, addon.id);
+  ok(!removeButton, "remove button is not shown");
+
+  yield tearDownAddon(addon);
+  yield closeAboutDebugging(tab);
+});
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -3,17 +3,18 @@
 
 /* eslint-env browser */
 /* exported openAboutDebugging, changeAboutDebuggingHash, closeAboutDebugging,
    installAddon, uninstallAddon, waitForMutation, waitForContentMutation, assertHasTarget,
    getServiceWorkerList, getTabList, openPanel, waitForInitialAddonList,
    waitForServiceWorkerRegistered, unregisterServiceWorker,
    waitForDelayedStartupFinished, setupTestAboutDebuggingWebExtension,
    waitForServiceWorkerActivation, enableServiceWorkerDebugging,
-   getServiceWorkerContainer */
+   getServiceWorkerContainer, promiseAddonEvent, installAddonWithManager, getAddonByID,
+   tearDownAddon */
 /* import-globals-from ../../framework/test/shared-head.js */
 
 "use strict";
 
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
@@ -431,8 +432,63 @@ function enableServiceWorkerDebugging() 
       ["dom.serviceWorkers.testing.enabled", true],
       // Force single content process.
       ["dom.ipc.processCount", 1],
     ]};
     SpecialPowers.pushPrefEnv(options, done);
     Services.ppmm.releaseCachedProcesses();
   });
 }
+
+/**
+ * Returns a promise that resolves when the given add-on event is fired. The
+ * resolved value is an array of arguments passed for the event.
+ */
+function promiseAddonEvent(event) {
+  return new Promise(resolve => {
+    let listener = {
+      [event]: function (...args) {
+        AddonManager.removeAddonListener(listener);
+        resolve(args);
+      }
+    };
+
+    AddonManager.addAddonListener(listener);
+  });
+}
+
+/**
+ * Install an add-on using the AddonManager so it does not show up as temporary.
+ */
+function installAddonWithManager(filePath) {
+  return new Promise((resolve, reject) => {
+    AddonManager.getInstallForFile(filePath, install => {
+      if (!install) {
+        throw new Error(`An install was not created for ${filePath}`);
+      }
+      install.addListener({
+        onDownloadFailed: reject,
+        onDownloadCancelled: reject,
+        onInstallFailed: reject,
+        onInstallCancelled: reject,
+        onInstallEnded: resolve
+      });
+      install.install();
+    });
+  });
+}
+
+function getAddonByID(addonId) {
+  return new Promise(resolve => {
+    AddonManager.getAddonByID(addonId, addon => resolve(addon));
+  });
+}
+
+/**
+ * Uninstall an add-on.
+ */
+function* tearDownAddon(addon) {
+  const onUninstalled = promiseAddonEvent("onUninstalled");
+  addon.uninstall();
+  const [uninstalledAddon] = yield onUninstalled;
+  is(uninstalledAddon.id, addon.id,
+     `Add-on was uninstalled: ${uninstalledAddon.id}`);
+}
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -73,17 +73,16 @@ support-files =
   doc_conditional-breakpoints.html
   doc_domnode-variables.html
   doc_editor-mode.html
   doc_empty-tab-01.html
   doc_empty-tab-02.html
   doc_event-listeners-01.html
   doc_event-listeners-02.html
   doc_event-listeners-03.html
-  doc_event-listeners-04.html
   doc_frame-parameters.html
   doc_function-display-name.html
   doc_function-jump.html
   doc_function-search.html
   doc_global-method-override.html
   doc_iframes.html
   doc_included-script.html
   doc_inline-debugger-statement.html
@@ -177,18 +176,16 @@ skip-if = e10s || true # bug 1113935
 [browser_dbg_break-on-dom-05.js]
 [browser_dbg_break-on-dom-06.js]
 [browser_dbg_break-on-dom-07.js]
 [browser_dbg_break-on-dom-08.js]
 [browser_dbg_break-on-dom-event-01.js]
 skip-if = e10s || os == "mac" || e10s # Bug 895426
 [browser_dbg_break-on-dom-event-02.js]
 skip-if = e10s # TODO
-[browser_dbg_break-on-dom-event-03.js]
-skip-if = e10s # TODO
 [browser_dbg_break-unselected.js]
 [browser_dbg_breakpoints-actual-location.js]
 [browser_dbg_breakpoints-actual-location2.js]
 [browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
 skip-if = e10s # Bug 1093535
 [browser_dbg_breakpoints-button-01.js]
 [browser_dbg_breakpoints-button-02.js]
 [browser_dbg_breakpoints-condition-thrown-message.js]
@@ -264,18 +261,16 @@ skip-if = e10s && debug
 [browser_dbg_editor-mode.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-01.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-02.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-03.js]
 skip-if = e10s && debug
-[browser_dbg_event-listeners-04.js]
-skip-if = debug || e10s # debug bug 1142597, e10s bug 1146603.
 [browser_dbg_file-reload.js]
 skip-if = e10s && debug
 [browser_dbg_function-display-name.js]
 skip-if = e10s && debug
 [browser_dbg_global-method-override.js]
 skip-if = e10s && debug
 [browser_dbg_globalactor.js]
 skip-if = e10s # TODO
--- a/devtools/client/debugger/test/mochitest/browser2.ini
+++ b/devtools/client/debugger/test/mochitest/browser2.ini
@@ -73,17 +73,16 @@ support-files =
   doc_conditional-breakpoints.html
   doc_domnode-variables.html
   doc_editor-mode.html
   doc_empty-tab-01.html
   doc_empty-tab-02.html
   doc_event-listeners-01.html
   doc_event-listeners-02.html
   doc_event-listeners-03.html
-  doc_event-listeners-04.html
   doc_frame-parameters.html
   doc_function-display-name.html
   doc_function-jump.html
   doc_function-search.html
   doc_global-method-override.html
   doc_iframes.html
   doc_included-script.html
   doc_inline-debugger-statement.html
deleted file mode 100644
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests that the break-on-dom-events request works for load event listeners.
- */
-
-const TAB_URL = EXAMPLE_URL + "doc_event-listeners-04.html";
-
-var gClient, gThreadClient;
-
-function test() {
-  if (!DebuggerServer.initialized) {
-    DebuggerServer.init();
-    DebuggerServer.addBrowserActors();
-  }
-
-  let transport = DebuggerServer.connectPipe();
-  gClient = new DebuggerClient(transport);
-  gClient.connect().then(([aType, aTraits]) => {
-    is(aType, "browser",
-      "Root actor should identify itself as a browser.");
-
-    addTab(TAB_URL)
-      .then(() => attachThreadActorForUrl(gClient, TAB_URL))
-      .then(aThreadClient => gThreadClient = aThreadClient)
-      .then(pauseDebuggee)
-      .then(testBreakOnLoad)
-      .then(() => gClient.close())
-      .then(finish)
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
-  });
-}
-
-function pauseDebuggee() {
-  let deferred = promise.defer();
-
-  gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
-    is(aPacket.type, "paused",
-      "We should now be paused.");
-    is(aPacket.why.type, "debuggerStatement",
-      "The debugger statement was hit.");
-
-    gThreadClient.resume(deferred.resolve);
-  });
-
-  // Spin the event loop before causing the debuggee to pause, to allow
-  // this function to return first.
-  executeSoon(() => triggerButtonClick());
-
-  return deferred.promise;
-}
-
-// Test pause on a load event.
-function testBreakOnLoad() {
-  let deferred = promise.defer();
-
-  // Test calling pauseOnDOMEvents from a running state.
-  gThreadClient.pauseOnDOMEvents(["load"], (aPacket) => {
-    is(aPacket.error, undefined,
-      "The pause-on-load request completed successfully.");
-    let handlers = ["loadHandler"];
-
-    gClient.addListener("paused", function tester(aEvent, aPacket) {
-      is(aPacket.why.type, "pauseOnDOMEvents",
-        "A hidden breakpoint was hit.");
-
-      is(aPacket.frame.where.line, 15, "Found the load event listener.");
-      gClient.removeListener("paused", tester);
-      deferred.resolve();
-
-      gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1)));
-    });
-
-    getTabActorForUrl(gClient, TAB_URL).then(aGrip => {
-      gClient.attachTab(aGrip.actor, (aResponse, aTabClient) => {
-        aTabClient.reload();
-      });
-    });
-  });
-
-  return deferred.promise;
-}
-
-function triggerButtonClick() {
-  let button = content.document.querySelector("button");
-  EventUtils.sendMouseEvent({ type: "click" }, button);
-}
-
-registerCleanupFunction(function () {
-  gClient = null;
-  gThreadClient = null;
-});
deleted file mode 100644
--- a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Test that event listeners are properly fetched even if one of the listeners
- * don't have a Debugger.Source object (bug 942899).
- *
- * This test is skipped on debug and e10s builds for following reasons:
- *  - debug: requiring sdk/tabs causes memory leaks when new windows are opened
- *    in tests executed after this one. Bug 1142597.
- *  - e10s: tab.attach is not e10s safe and only works when add-on compatibility
- *    shims are in place. Bug 1146603.
- */
-
-const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html";
-
-function test() {
-  Task.spawn(function* () {
-    let tab = yield addTab(TAB_URL);
-
-    // Create a sandboxed content script the Add-on SDK way. Inspired by bug
-    // 1145996.
-    let tabs = require("sdk/tabs");
-    let sdkTab = [...tabs].find(tab => tab.url === TAB_URL);
-    ok(sdkTab, "Add-on SDK found the loaded tab.");
-
-    info("Attaching an event handler via add-on sdk content scripts.");
-    let worker = sdkTab.attach({
-      contentScript: "document.body.addEventListener('click', e => alert(e))",
-      onError: ok.bind(this, false)
-    });
-
-    let options = {
-      source: TAB_URL,
-      line: 1
-    };
-    let [,, panel, win] = yield initDebugger(tab, options);
-    let dbg = panel.panelWin;
-    let controller = dbg.DebuggerController;
-    let constants = dbg.require("./content/constants");
-    let actions = dbg.require("./content/actions/event-listeners");
-    let fetched = waitForDispatch(panel, constants.FETCH_EVENT_LISTENERS);
-
-    info("Scheduling event listener fetch.");
-    controller.dispatch(actions.fetchEventListeners());
-
-    info("Waiting for updated event listeners to arrive.");
-    yield fetched;
-
-    ok(true, "The listener update did not hang.");
-    closeDebuggerAndFinish(panel);
-  });
-}
deleted file mode 100644
--- a/devtools/client/debugger/test/mochitest/doc_event-listeners-04.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!-- Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/ -->
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8"/>
-    <title>Debugger test page</title>
-  </head>
-
-  <body>
-    <button>Click me!</button>
-
-    <script type="text/javascript">
-      window.addEventListener("load", function onload() {
-        var button = document.querySelector("button");
-        button.onclick = function () {
-          debugger;
-        };
-      });
-    </script>
-  </body>
-
-</html>
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -92,20 +92,19 @@ webExtTip.learnMore = Learn more
 # This string is displayed as the title of the file picker that appears when
 # the user clicks the 'Load Temporary Add-on' button
 selectAddonFromFile2 = Select Manifest File or Package (.xpi)
 
 # LOCALIZATION NOTE (reload):
 # This string is displayed as a label of the button that reloads a given addon.
 reload = Reload
 
-# LOCALIZATION NOTE (reloadDisabledTooltip):
-# This string is displayed in a tooltip that appears when hovering over a
-# disabled 'reload' button.
-reloadDisabledTooltip = Only temporarily installed add-ons can be reloaded
+# LOCALIZATION NOTE (remove):
+# This string is displayed as a label of the button that will remove a given addon.
+remove = Remove
 
 # LOCALIZATION NOTE (location):
 # This string is displayed as a label for the filesystem location of an extension.
 location = Location
 
 # LOCALIZATION NOTE (workers):
 # This string is displayed as a header of the about:debugging#workers page.
 workers = Workers
--- a/devtools/client/netmonitor/src/utils/request-utils.js
+++ b/devtools/client/netmonitor/src/utils/request-utils.js
@@ -138,17 +138,17 @@ function getUrlBaseName(url) {
 
 /**
  * Helpers for getting the query portion of a url.
  *
  * @param {string} url - url string
  * @return {string} unicode query of a url
  */
 function getUrlQuery(url) {
-  return decodeUnicodeUrl((new URL(url)).search.replace(/^\?/, ""));
+  return (new URL(url)).search.replace(/^\?/, "");
 }
 
 /**
  * Helpers for getting unicode name and query portions of a url.
  *
  * @param {string} url - url string
  * @return {string} unicode basename and query portions of a url
  */
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -411,17 +411,18 @@ function verifyRequestItemTarget(documen
   if (fuzzyUrl) {
     ok(target.querySelector(".requests-list-file").textContent.startsWith(
       name + (query ? "?" + query : "")), "The displayed file is correct.");
     ok(target.querySelector(".requests-list-file").getAttribute("title")
                                                   .startsWith(unicodeUrl),
       "The tooltip file is correct.");
   } else {
     is(target.querySelector(".requests-list-file").textContent,
-      name + (query ? "?" + query : ""), "The displayed file is correct.");
+      decodeURIComponent(name + (query ? "?" + query : "")),
+      "The displayed file is correct.");
     is(target.querySelector(".requests-list-file").getAttribute("title"),
       unicodeUrl, "The tooltip file is correct.");
   }
 
   is(target.querySelector(".requests-list-protocol").textContent,
     httpVersion, "The displayed protocol is correct.");
 
   is(target.querySelector(".requests-list-protocol").getAttribute("title"),
--- a/devtools/client/performance/test/helpers/tab-utils.js
+++ b/devtools/client/performance/test/helpers/tab-utils.js
@@ -1,18 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 /* globals dump */
 
+const { Cu } = require("chrome");
+const { BrowserTestUtils } = Cu.import("resource://testing-common/BrowserTestUtils.jsm", {});
 const Services = require("Services");
-const tabs = require("sdk/tabs");
-const tabUtils = require("sdk/tabs/utils");
-const { viewFor } = require("sdk/view/core");
 const { waitForDelayedStartupFinished } = require("devtools/client/performance/test/helpers/wait-utils");
 const { gDevTools } = require("devtools/client/framework/devtools");
 
 /**
  * Gets a random integer in between an interval. Used to uniquely identify
  * added tabs by augmenting the URL.
  */
 function getRandomInt(min, max) {
@@ -24,54 +23,29 @@ function getRandomInt(min, max) {
  * for it to load.
  */
 exports.addTab = function ({ url, win }, options = {}) {
   let id = getRandomInt(0, Number.MAX_SAFE_INTEGER - 1);
   url += `#${id}`;
 
   dump(`Adding tab with url: ${url}.\n`);
 
-  return new Promise(resolve => {
-    let tab;
-
-    tabs.on("ready", function onOpen(model) {
-      if (tab != viewFor(model)) {
-        return;
-      }
-      dump(`Tab added and finished loading: ${model.url}.\n`);
-      tabs.off("ready", onOpen);
-      resolve(tab);
-    });
-
-    win.focus();
-    tab = tabUtils.openTab(win, url);
-
-    if (options.dontWaitForTabReady) {
-      resolve(tab);
-    }
-  });
+  let { gBrowser } = win || window;
+  return BrowserTestUtils.openNewForegroundTab(gBrowser, url,
+                                               !options.dontWaitForTabReady);
 };
 
 /**
  * Removes a browser tab from the specified window and waits for it to close.
  */
 exports.removeTab = function (tab, options = {}) {
-  dump(`Removing tab: ${tabUtils.getURI(tab)}.\n`);
+  dump(`Removing tab: ${tab.linkedBrowser.currentURI.spec}.\n`);
 
   return new Promise(resolve => {
-    tabs.on("close", function onClose(model) {
-      if (tab != viewFor(model)) {
-        return;
-      }
-      dump(`Tab removed and finished closing: ${model.url}.\n`);
-      tabs.off("close", onClose);
-      resolve(tab);
-    });
-
-    tabUtils.closeTab(tab);
+    BrowserTestUtils.removeTab(tab).then(() => resolve(tab));
 
     if (options.dontWaitForTabClose) {
       resolve(tab);
     }
   });
 };
 
 /**
--- a/devtools/client/sourceeditor/debugger.js
+++ b/devtools/client/sourceeditor/debugger.js
@@ -1,16 +1,15 @@
 /* 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 DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const promise = require("promise");
 const dbginfo = new WeakMap();
 
 // These functions implement search within the debugger. Since
 // search in the debugger is different from other components,
 // we can't use search.js CodeMirror addon. This is a slightly
 // modified version of that addon. Depends on searchcursor.js.
 
 function SearchState() {
@@ -125,57 +124,57 @@ function hasBreakpoint(ctx, line) {
 /**
  * Adds a visual breakpoint for a specified line. Third
  * parameter 'cond' can hold any object.
  *
  * After adding a breakpoint, this function makes Editor to
  * emit a breakpointAdded event.
  */
 function addBreakpoint(ctx, line, cond) {
-  function _addBreakpoint() {
-    let { ed, cm } = ctx;
-    let meta = dbginfo.get(ed);
-    let info = cm.lineInfo(line);
-
-    // The line does not exist in the editor. This is harmless, the
-    // architecture calling this assumes the editor will handle this
-    // gracefully, and make sure breakpoints exist when they need to.
-    if (!info) {
-      return;
-    }
-
-    ed.addLineClass(line, "breakpoint");
-    meta.breakpoints[line] = { condition: cond };
-
-    // TODO(jwl): why is `info` null when breaking on page reload?
-    info.handle.on("delete", function onDelete() {
-      info.handle.off("delete", onDelete);
-      meta.breakpoints[info.line] = null;
-    });
-
-    if (cond) {
-      setBreakpointCondition(ctx, line);
-    }
-    ed.emit("breakpointAdded", line);
-    deferred.resolve();
-  }
-
   if (hasBreakpoint(ctx, line)) {
     return null;
   }
 
-  let deferred = promise.defer();
-  // If lineInfo() returns null, wait a tick to give the editor a chance to
-  // initialize properly.
-  if (ctx.cm.lineInfo(line) === null) {
-    DevToolsUtils.executeSoon(() => _addBreakpoint());
-  } else {
-    _addBreakpoint();
-  }
-  return deferred.promise;
+  return new Promise(resolve => {
+    function _addBreakpoint() {
+      let { ed, cm } = ctx;
+      let meta = dbginfo.get(ed);
+      let info = cm.lineInfo(line);
+
+      // The line does not exist in the editor. This is harmless, the
+      // architecture calling this assumes the editor will handle this
+      // gracefully, and make sure breakpoints exist when they need to.
+      if (!info) {
+        return;
+      }
+
+      ed.addLineClass(line, "breakpoint");
+      meta.breakpoints[line] = { condition: cond };
+
+      // TODO(jwl): why is `info` null when breaking on page reload?
+      info.handle.on("delete", function onDelete() {
+        info.handle.off("delete", onDelete);
+        meta.breakpoints[info.line] = null;
+      });
+
+      if (cond) {
+        setBreakpointCondition(ctx, line);
+      }
+      ed.emit("breakpointAdded", line);
+      resolve();
+    }
+
+    // If lineInfo() returns null, wait a tick to give the editor a chance to
+    // initialize properly.
+    if (ctx.cm.lineInfo(line) === null) {
+      DevToolsUtils.executeSoon(() => _addBreakpoint());
+    } else {
+      _addBreakpoint();
+    }
+  });
 }
 
 /**
  * Helps reset the debugger's breakpoint state
  * - removes the breakpoints in the editor
  * - cleares the debugger's breakpoint state
  *
  * Note, does not *actually* remove a source's breakpoints.
--- a/devtools/client/sourceeditor/editor.js
+++ b/devtools/client/sourceeditor/editor.js
@@ -25,17 +25,16 @@ const VALID_KEYMAPS = new Set(["emacs", 
 const MAX_VERTICAL_OFFSET = 3;
 
 // Match @Scratchpad/N:LINE[:COLUMN] or (LINE[:COLUMN]) anywhere at an end of
 // line in text selection.
 const RE_SCRATCHPAD_ERROR = /(?:@Scratchpad\/\d+:|\()(\d+):?(\d+)?(?:\)|\n)/;
 const RE_JUMP_TO_LINE = /^(\d+):?(\d+)?/;
 
 const Services = require("Services");
-const promise = require("promise");
 const events = require("devtools/shared/event-emitter");
 const { PrefObserver } = require("devtools/client/shared/prefs");
 const { getClientCssProperties } = require("devtools/shared/fronts/css-properties");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/sourceeditor.properties");
 
@@ -252,55 +251,55 @@ Editor.prototype = {
    * Appends the current Editor instance to the element specified by
    * 'el'. You can also provide your won iframe to host the editor as
    * an optional second parameter. This method actually creates and
    * loads CodeMirror and all its dependencies.
    *
    * This method is asynchronous and returns a promise.
    */
   appendTo: function (el, env) {
-    let def = promise.defer();
-    let cm = editors.get(this);
+    return new Promise(resolve => {
+      let cm = editors.get(this);
 
-    if (!env) {
-      env = el.ownerDocument.createElementNS(el.namespaceURI, "iframe");
+      if (!env) {
+        env = el.ownerDocument.createElementNS(el.namespaceURI, "iframe");
 
-      if (el.namespaceURI === XUL_NS) {
-        env.flex = 1;
+        if (el.namespaceURI === XUL_NS) {
+          env.flex = 1;
+        }
       }
-    }
 
-    if (cm) {
-      throw new Error("You can append an editor only once.");
-    }
-
-    let onLoad = () => {
-      let win = env.contentWindow.wrappedJSObject;
-
-      if (!this.config.themeSwitching) {
-        win.document.documentElement.setAttribute("force-theme", "light");
+      if (cm) {
+        throw new Error("You can append an editor only once.");
       }
 
-      Services.scriptloader.loadSubScript(
-        "chrome://devtools/content/shared/theme-switching.js",
-        win, "utf8"
-      );
-      this.container = env;
-      this._setup(win.document.body, el.ownerDocument);
-      env.removeEventListener("load", onLoad, true);
+      let onLoad = () => {
+        let win = env.contentWindow.wrappedJSObject;
+
+        if (!this.config.themeSwitching) {
+          win.document.documentElement.setAttribute("force-theme", "light");
+        }
 
-      def.resolve();
-    };
+        Services.scriptloader.loadSubScript(
+          "chrome://devtools/content/shared/theme-switching.js",
+          win, "utf8"
+        );
+        this.container = env;
+        this._setup(win.document.body, el.ownerDocument);
+        env.removeEventListener("load", onLoad, true);
 
-    env.addEventListener("load", onLoad, true);
-    env.setAttribute("src", CM_IFRAME);
-    el.appendChild(env);
+        resolve();
+      };
 
-    this.once("destroy", () => el.removeChild(env));
-    return def.promise;
+      env.addEventListener("load", onLoad, true);
+      env.setAttribute("src", CM_IFRAME);
+      el.appendChild(env);
+
+      this.once("destroy", () => el.removeChild(env));
+    });
   },
 
   appendToLocalElement: function (el) {
     this._setup(el);
   },
 
   /**
    * Do the actual appending and configuring of the CodeMirror instance. This is
--- a/devtools/client/sourceeditor/test/browser_editor_autocomplete_js.js
+++ b/devtools/client/sourceeditor/test/browser_editor_autocomplete_js.js
@@ -24,22 +24,20 @@ function testJS(ed, win) {
 
   ok(ed.getOption("autocomplete"), "Autocompletion is set");
   ok(win.tern, "Tern is defined on the window");
 
   ed.focus();
   ed.setText("document.");
   ed.setCursor({line: 0, ch: 9});
 
-  let waitForSuggestion = promise.defer();
+  return new Promise(resolve => {
+    ed.on("before-suggest", () => {
+      info("before-suggest has been triggered");
+      EventUtils.synthesizeKey("VK_ESCAPE", { }, win);
+      resolve();
+    });
 
-  ed.on("before-suggest", () => {
-    info("before-suggest has been triggered");
-    EventUtils.synthesizeKey("VK_ESCAPE", { }, win);
-    waitForSuggestion.resolve();
+    let autocompleteKey =
+      Editor.keyFor("autocompletion", { noaccel: true }).toUpperCase();
+    EventUtils.synthesizeKey("VK_" + autocompleteKey, { ctrlKey: true }, win);
   });
-
-  let autocompleteKey =
-    Editor.keyFor("autocompletion", { noaccel: true }).toUpperCase();
-  EventUtils.synthesizeKey("VK_" + autocompleteKey, { ctrlKey: true }, win);
-
-  return waitForSuggestion.promise;
 }
--- a/devtools/client/sourceeditor/test/head.js
+++ b/devtools/client/sourceeditor/test/head.js
@@ -23,56 +23,55 @@ SimpleTest.registerCleanupFunction(() =>
 
 function promiseWaitForFocus() {
   return new Promise(resolve =>
     waitForFocus(resolve));
 }
 
 function setup(cb, additionalOpts = {}) {
   cb = cb || function () {};
-  let def = promise.defer();
-  const opt = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
-  const url = "data:application/vnd.mozilla.xul+xml;charset=UTF-8," +
-    "<?xml version='1.0'?>" +
-    "<?xml-stylesheet href='chrome://global/skin/global.css'?>" +
-    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper" +
-    "/there.is.only.xul' title='Editor' width='600' height='500'>" +
-    "<box flex='1'/></window>";
+  return new Promise(resolve => {
+    const opt = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+    const url = "data:application/vnd.mozilla.xul+xml;charset=UTF-8," +
+      "<?xml version='1.0'?>" +
+      "<?xml-stylesheet href='chrome://global/skin/global.css'?>" +
+      "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper" +
+      "/there.is.only.xul' title='Editor' width='600' height='500'>" +
+      "<box flex='1'/></window>";
 
-  let win = Services.ww.openWindow(null, url, "_blank", opt, null);
-  let opts = {
-    value: "Hello.",
-    lineNumbers: true,
-    foldGutter: true,
-    gutters: ["CodeMirror-linenumbers", "breakpoints", "CodeMirror-foldgutter"],
-    cssProperties: getClientCssProperties()
-  };
+    let win = Services.ww.openWindow(null, url, "_blank", opt, null);
+    let opts = {
+      value: "Hello.",
+      lineNumbers: true,
+      foldGutter: true,
+      gutters: ["CodeMirror-linenumbers", "breakpoints", "CodeMirror-foldgutter"],
+      cssProperties: getClientCssProperties()
+    };
 
-  for (let o in additionalOpts) {
-    opts[o] = additionalOpts[o];
-  }
+    for (let o in additionalOpts) {
+      opts[o] = additionalOpts[o];
+    }
 
-  win.addEventListener("load", function () {
-    waitForFocus(function () {
-      let box = win.document.querySelector("box");
-      let editor = new Editor(opts);
+    win.addEventListener("load", function () {
+      waitForFocus(function () {
+        let box = win.document.querySelector("box");
+        let editor = new Editor(opts);
 
-      editor.appendTo(box)
-        .then(() => {
-          def.resolve({
-            ed: editor,
-            win: win,
-            edWin: editor.container.contentWindow.wrappedJSObject
-          });
-          cb(editor, win);
-        }, err => ok(false, err.message));
-    }, win);
-  }, {once: true});
-
-  return def.promise;
+        editor.appendTo(box)
+          .then(() => {
+            resolve({
+              ed: editor,
+              win: win,
+              edWin: editor.container.contentWindow.wrappedJSObject
+            });
+            cb(editor, win);
+          }, err => ok(false, err.message));
+      }, win);
+    }, {once: true});
+  });
 }
 
 function ch(exp, act, label) {
   is(exp.line, act.line, label + " (line)");
   is(exp.ch, act.ch, label + " (ch)");
 }
 
 function teardown(ed, win) {
--- a/devtools/shared/gcli/command-state.js
+++ b/devtools/shared/gcli/command-state.js
@@ -1,19 +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/. */
 
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 
-loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
-
-const getTargetId = ({tab}) => getBrowserForTab(tab).outerWindowID;
+const getTargetId = ({tab}) => tab.linkedBrowser.outerWindowID;
 const enabledCommands = new Map();
 
 /**
  * The `CommandState` is a singleton that provides utility methods to keep the commands'
  * state in sync between the toolbox, the toolbar and the content.
  */
 const CommandState = EventEmitter.decorate({
   /**
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4258,33 +4258,16 @@ ContentParent::RecvUnregisterRemoteFrame
 mozilla::ipc::IPCResult
 ContentParent::RecvNotifyTabDestroying(const TabId& aTabId,
                                        const ContentParentId& aCpId)
 {
   NotifyTabDestroying(aTabId, aCpId);
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-ContentParent::RecvTabChildNotReady(const TabId& aTabId)
-{
-  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
-  RefPtr<TabParent> tp =
-    cpm->GetTopLevelTabParentByProcessAndTabId(this->ChildID(), aTabId);
-
-  if (!tp) {
-    NS_WARNING("Couldn't find TabParent for TabChildNotReady message.");
-    return IPC_OK();
-  }
-
-  tp->DispatchTabChildNotReadyEvent();
-
-  return IPC_OK();
-}
-
 nsTArray<TabContext>
 ContentParent::GetManagedTabContext()
 {
   return Move(ContentProcessManager::GetSingleton()->
           GetTabContextByContentProcess(this->ChildID()));
 }
 
 mozilla::docshell::POfflineCacheUpdateParent*
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -488,18 +488,16 @@ public:
 
   virtual mozilla::ipc::IPCResult RecvUnregisterRemoteFrame(const TabId& aTabId,
                                                             const ContentParentId& aCpId,
                                                             const bool& aMarkedDestroying) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyTabDestroying(const TabId& aTabId,
                                                           const ContentParentId& aCpId) override;
 
-  virtual mozilla::ipc::IPCResult RecvTabChildNotReady(const TabId& aTabId) override;
-
   nsTArray<TabContext> GetManagedTabContext();
 
   virtual POfflineCacheUpdateParent*
   AllocPOfflineCacheUpdateParent(const URIParams& aManifestURI,
                                  const URIParams& aDocumentURI,
                                  const PrincipalInfo& aLoadingPrincipalInfo,
                                  const bool& aStickDocument) override;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -925,19 +925,16 @@ parent:
                                 ContentParentId cpId,
                                 bool aMarkedDestroying);
 
     /**
      * Tell the chrome process there is a destruction of PBrowser(Tab)
      */
     async NotifyTabDestroying(TabId tabId,
                               ContentParentId cpId);
-
-    async TabChildNotReady(TabId tabId);
-
     /**
      * Starts an offline application cache update.
      * @param manifestURI
      *   URI of the manifest to fetch, the application cache group ID
      * @param documentURI
      *   URI of the document that referred the manifest
      * @param loadingPrincipal
      *   Principal of the document that referred the manifest
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -28,17 +28,16 @@ union HangData
   PluginHangData;
 };
 
 protocol PProcessHangMonitor
 {
 parent:
   async HangEvidence(HangData data);
   async ClearHang();
-  async Ready();
 
 child:
   async TerminateScript();
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
 
   async ForcePaint(TabId tabId, uint64_t aLayerObserverEpoch);
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -7,17 +7,16 @@
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 
 #include "jsapi.h"
 #include "js/GCAPI.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/BackgroundHangMonitor.h"
-#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/ipc/TaskFactory.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/plugins/PluginBridge.h"
@@ -188,18 +187,16 @@ public:
     mDumpId = aDumpId;
   }
 
   void ClearHang() {
     mHangData = HangData();
     mDumpId.Truncate();
   }
 
-  void DispatchTabChildNotReady(TabId aTabId);
-
 private:
   ~HangMonitoredProcess() = default;
 
   // Everything here is main thread-only.
   HangMonitorParent* mActor;
   ContentParent* mContentParent;
   HangData mHangData;
   nsAutoString mDumpId;
@@ -209,17 +206,16 @@ class HangMonitorParent
   : public PProcessHangMonitorParent
 {
 public:
   explicit HangMonitorParent(ProcessHangMonitor* aMonitor);
   ~HangMonitorParent() override;
 
   void Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint);
 
-  mozilla::ipc::IPCResult RecvReady() override;
   mozilla::ipc::IPCResult RecvHangEvidence(const HangData& aHangData) override;
   mozilla::ipc::IPCResult RecvClearHang() override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; }
 
   void Shutdown();
@@ -244,33 +240,25 @@ private:
   bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId);
 
   void SendHangNotification(const HangData& aHangData,
                             const nsString& aBrowserDumpId,
                             bool aTakeMinidump);
 
   void ClearHangNotification();
 
-  void DispatchTabChildNotReady(TabId aTabId);
-
   void ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch);
 
   void ShutdownOnThread();
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
 
   // This field is read-only after construction.
   bool mReportHangs;
 
-  // This field is only accessed on the hang thread. Inits to
-  // false, and will flip to true once the HangMonitorChild is
-  // constructed in the child process, and sends a message saying
-  // so.
-  bool mReady;
-
   // This field is only accessed on the hang thread.
   bool mIPCOpen;
 
   Monitor mMonitor;
 
   // Must be accessed with mMonitor held.
   RefPtr<HangMonitoredProcess> mProcess;
   bool mShutdownDone;
@@ -334,21 +322,16 @@ HangMonitorChild::InterruptCallback()
     mForcePaint = false;
   }
 
   if (forcePaint) {
     RefPtr<TabChild> tabChild = TabChild::FindTabChild(forcePaintTab);
     if (tabChild) {
       js::AutoAssertNoContentJS nojs(mContext);
       tabChild->ForcePaint(forcePaintEpoch);
-    } else {
-      auto cc = ContentChild::GetSingleton();
-      if (cc) {
-        cc->SendTabChildNotReady(forcePaintTab);
-      }
     }
   }
 }
 
 void
 HangMonitorChild::Shutdown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
@@ -444,18 +427,16 @@ HangMonitorChild::Bind(Endpoint<PProcess
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   MOZ_ASSERT(!sInstance);
   sInstance = this;
 
   DebugOnly<bool> ok = aEndpoint.Bind(this);
   MOZ_ASSERT(ok);
-
-  Unused << SendReady();
 }
 
 void
 HangMonitorChild::NotifySlowScriptAsync(TabId aTabId,
                                         const nsCString& aFileName)
 {
   if (mIPCOpen) {
     Unused << SendHangEvidence(SlowScriptData(aTabId, aFileName));
@@ -566,17 +547,16 @@ HangMonitorChild::ClearHangAsync()
     Unused << SendClearHang();
   }
 }
 
 /* HangMonitorParent implementation */
 
 HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor)
  : mHangMonitor(aMonitor),
-   mReady(false),
    mIPCOpen(true),
    mMonitor("HangMonitorParent lock"),
    mShutdownDone(false),
    mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock"),
    mMainThreadTaskFactory(this)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false);
@@ -647,47 +627,22 @@ HangMonitorParent::ForcePaint(dom::TabPa
   if (sShouldForcePaint) {
     TabId id = aTab->GetTabId();
     MonitorLoop()->PostTask(NewNonOwningRunnableMethod<TabId, uint64_t>(
                               this, &HangMonitorParent::ForcePaintOnThread, id, aLayerObserverEpoch));
   }
 }
 
 void
-HangMonitorParent::DispatchTabChildNotReady(TabId aTabId)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  if (!mProcess) {
-    return;
-  }
-
-  mProcess->DispatchTabChildNotReady(aTabId);
-}
-
-void
 HangMonitorParent::ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   if (mIPCOpen) {
-    if (mReady) {
-      Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
-    } else {
-      // We've never heard from the HangMonitorChild before, so
-      // it's either not finished setting up, or has only recently
-      // finished setting up. In either case, we're dealing with
-      // a new content process that probably hasn't had time to
-      // get the ContentChild, let alone the TabChild for aTabId,
-      // set up, and so attempting to force paint on the non-existant
-      // TabChild is not going to work. Instead, we tell the main
-      // thread that we're waiting on a TabChild to be created.
-      NS_DispatchToMainThread(
-        mMainThreadTaskFactory.NewRunnableMethod(
-          &HangMonitorParent::DispatchTabChildNotReady, aTabId));
-    }
+    Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
   }
 }
 
 void
 HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
   mIPCOpen = false;
@@ -766,24 +721,16 @@ HangMonitorParent::TakeBrowserMinidump(c
     }
   }
 #endif // MOZ_CRASHREPORTER
 
   return false;
 }
 
 mozilla::ipc::IPCResult
-HangMonitorParent::RecvReady()
-{
-  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
-  mReady = true;
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
 HangMonitorParent::RecvHangEvidence(const HangData& aHangData)
 {
   // chrome process, background thread
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   if (!mReportHangs) {
     return IPC_OK();
   }
@@ -1078,27 +1025,16 @@ HangMonitoredProcess::UserCanceled()
 
   if (mActor) {
     uint32_t id = mHangData.get_PluginHangData().pluginId();
     mActor->CleanupPluginHang(id, true);
   }
   return NS_OK;
 }
 
-void
-HangMonitoredProcess::DispatchTabChildNotReady(TabId aTabId)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  if (!mContentParent) {
-    return;
-  }
-
-  Unused << mContentParent->RecvTabChildNotReady(aTabId);
-}
-
 static bool
 InterruptCallback(JSContext* cx)
 {
   if (HangMonitorChild* child = HangMonitorChild::Get()) {
     child->InterruptCallback();
   }
 
   return true;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3262,62 +3262,16 @@ TabParent::LiveResizeStarted()
 }
 
 void
 TabParent::LiveResizeStopped()
 {
   SuppressDisplayport(false);
 }
 
-void
-TabParent::DispatchTabChildNotReadyEvent()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
-  if (!target) {
-    NS_WARNING("Could not locate target for tab child not ready event.");
-    return;
-  }
-
-  if (mHasPresented) {
-    // We shouldn't dispatch this event because clearly the
-    // TabChild _became_ ready by the time we were told to
-    // dispatch.
-    return;
-  }
-
-  if (!mDocShellIsActive) {
-    return;
-  }
-
-  RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true);
-  if (!frameLoader) {
-    return;
-  }
-
-  nsCOMPtr<Element> frameElement(mFrameElement);
-  nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(frameElement);
-  if (!owner) {
-    return;
-  }
-
-  RefPtr<nsFrameLoader> currentFrameLoader = owner->GetFrameLoader();
-  if (currentFrameLoader != frameLoader) {
-    return;
-  }
-
-  RefPtr<Event> event = NS_NewDOMEvent(mFrameElement, nullptr, nullptr);
-  event->InitEvent(NS_LITERAL_STRING("MozTabChildNotReady"), true, false);
-  event->SetTrusted(true);
-  event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
-  bool dummy;
-  mFrameElement->DispatchEvent(event, &dummy);
-}
-
 NS_IMETHODIMP
 FakeChannel::OnAuthAvailable(nsISupports *aContext, nsIAuthInformation *aAuthInfo)
 {
   nsAuthInformationHolder* holder =
     static_cast<nsAuthInformationHolder*>(aAuthInfo);
 
   if (!net::gNeckoChild->SendOnAuthAvailable(mCallbackId,
                                              holder->User(),
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -582,18 +582,16 @@ public:
                           uint64_t* aLayersId);
 
   mozilla::ipc::IPCResult RecvEnsureLayersConnected(CompositorOptions* aCompositorOptions) override;
 
   // LiveResizeListener implementation
   void LiveResizeStarted() override;
   void LiveResizeStopped() override;
 
-  void DispatchTabChildNotReadyEvent();
-
 protected:
   bool ReceiveMessage(const nsString& aMessage,
                       bool aSync,
                       ipc::StructuredCloneData* aData,
                       mozilla::jsipc::CpowHolder* aCpows,
                       nsIPrincipal* aPrincipal,
                       nsTArray<ipc::StructuredCloneData>* aJSONRetVal = nullptr);
 
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -377,19 +377,19 @@ var interfaceNamesInGlobalScope =
     "GamepadAxisMoveEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "GamepadButtonEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "GamepadButton",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "GamepadEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "GamepadHapticActuator", release: false},
+    "GamepadHapticActuator",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "GamepadPose", release: false},
+    "GamepadPose",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HashChangeEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Headers",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "History",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HTMLAllCollection",
@@ -1172,31 +1172,31 @@ var interfaceNamesInGlobalScope =
     "UserProximityEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ValidityState",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "VideoPlaybackQuality",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "VideoStreamTrack",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRDisplay", release: false},
+    {name: "VRDisplay", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRDisplayCapabilities", release: false},
+    {name: "VRDisplayCapabilities", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRDisplayEvent", release: false},
+    {name: "VRDisplayEvent", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VREyeParameters", release: false},
+    {name: "VREyeParameters", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRFieldOfView", release: false},
+    {name: "VRFieldOfView", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRFrameData", release: false},
+    {name: "VRFrameData", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRPose", release: false},
+    {name: "VRPose", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRStageParameters", release: false},
+    {name: "VRStageParameters", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "VTTCue",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "VTTRegion", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WaveShaperNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "WebAuthnAssertion", disabled: true},
@@ -1317,16 +1317,17 @@ function createInterfaceMap(isXBLScope) 
       } else {
         ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
         if ((entry.nightly === !isNightly) ||
             (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) ||
             (entry.nonReleaseAndroid === !(isAndroid && !isRelease) && isAndroid) ||
             (entry.xbl === !isXBLScope) ||
             (entry.desktop === !isDesktop) ||
             (entry.windows === !isWindows) ||
+            (entry.releaseNonWindows === !isRelease && !isWindows) ||
             (entry.mac === !isMac) ||
             (entry.linux === !isLinux) ||
             (entry.android === !isAndroid && !entry.nonReleaseAndroid && !entry.nightlyAndroid) ||
             (entry.release === !isRelease) ||
             (entry.isSecureContext === !isSecureContext) ||
             entry.disabled) {
           interfaceMap[entry.name] = false;
         } else {
--- a/ipc/glue/Faulty.cpp
+++ b/ipc/glue/Faulty.cpp
@@ -101,38 +101,44 @@ void FuzzIntegralType(T* v, bool largeVa
                 "T must be an integral type");
   switch (random() % 6) {
     case 0:
       if (largeValues) {
         (*v) = RandomIntegral<T>();
         break;
       }
       // Fall through
+      MOZ_FALLTHROUGH;
+
     case 1:
       if (largeValues) {
         (*v) = RandomNumericLimit<T>();
         break;
       }
       // Fall through
+      MOZ_FALLTHROUGH;
+
     case 2:
       if (largeValues) {
         (*v) = RandomIntegerRange<T>(std::numeric_limits<T>::min(),
                                      std::numeric_limits<T>::max());
         break;
       }
       // Fall through
+      MOZ_FALLTHROUGH;
     default:
       switch(random() % 2) {
         case 0:
           // Prevent underflow
           if (*v != std::numeric_limits<T>::min()) {
             (*v)--;
             break;
           }
           // Fall through
+          MOZ_FALLTHROUGH;
         case 1:
           // Prevent overflow
           if (*v != std::numeric_limits<T>::max()) {
             (*v)++;
             break;
           }
       }
   }
@@ -149,41 +155,45 @@ void FuzzFloatingPointType(T* v, bool la
                 "T must be a floating point type");
   switch (random() % 6) {
     case 0:
       if (largeValues) {
         (*v) = RandomNumericLimit<T>();
         break;
     }
     // Fall through
+    MOZ_FALLTHROUGH;
     case 1:
       if (largeValues) {
         (*v) = RandomFloatingPointRange<T>(std::numeric_limits<T>::min(),
                                            std::numeric_limits<T>::max());
         break;
       }
     // Fall through
+    MOZ_FALLTHROUGH;
     default:
       (*v) = RandomFloatingPoint<T>();
   }
 }
 
 /**
  * FuzzStringType mutates an incercepted string type of a pickled message.
  */
 template <typename T>
 void FuzzStringType(T& v, const T& literal1, const T& literal2)
 {
   switch (random() % 5) {
     case 4:
       v = v + v;
       // Fall through
+      MOZ_FALLTHROUGH;
     case 3:
       v = v + v;
       // Fall through
+      MOZ_FALLTHROUGH;
     case 2:
       v = v + v;
       break;
     case 1:
       v += literal1;
       break;
     case 0:
       v = literal2;
@@ -517,17 +527,17 @@ Faulty::MutateUInt64(uint64_t* aValue)
 
 void
 Faulty::FuzzUInt64(uint64_t* aValue, unsigned int aProbability)
 {
   if (mIsValidProcessType) {
     if (mFuzzPickle && GetChance(aProbability)) {
       uint64_t oldValue = *aValue;
       MutateUInt64(aValue);
-      FAULTY_LOG("pickle field {UInt64} of value: %llu changed to: %llu",
+      FAULTY_LOG("pickle field {UInt64} of value: %" PRIu64 " changed to: %" PRIu64,
                  oldValue, *aValue);
     }
   }
 }
 
 void
 Faulty::MutateInt64(int64_t* aValue)
 {
@@ -536,17 +546,17 @@ Faulty::MutateInt64(int64_t* aValue)
 
 void
 Faulty::FuzzInt64(int64_t* aValue, unsigned int aProbability)
 {
   if (mIsValidProcessType) {
     if (mFuzzPickle && GetChance(aProbability)) {
       int64_t oldValue = *aValue;
       MutateInt64(aValue);
-      FAULTY_LOG("pickle field {Int64} of value: %lld changed to: %lld",
+      FAULTY_LOG("pickle field {UInt64} of value: %" PRIu64 " changed to: %" PRIu64,
                  oldValue, *aValue);
     }
   }
 }
 
 void
 Faulty::MutateDouble(double* aValue)
 {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1365782-1.js
@@ -0,0 +1,27 @@
+// --ion-eager --ion-offthread-compile=off
+
+var threw = 0;
+
+function f(t) {
+    for (var i = 0; i < 2; i++) {
+        try {
+            var x = 1;
+            Array(1);
+            x = 2;
+            x = t + t; // May throw if too large
+        } catch (e) {
+            assertEq(x, 2);
+            threw++;
+        }
+    }
+}
+
+var x = '$';
+for (var i = 0; i < 32; ++i) {
+    with({}){}
+    f(x);
+    try { x = x + x; } catch (e) { }
+}
+
+// Sanity check that throws happen
+assertEq(threw > 0, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1365782-2.js
@@ -0,0 +1,25 @@
+// --ion-eager --ion-offthread-compile=off
+
+function f(t) {
+    var would_throw = false;
+    try { t + t; } catch (e) { would_throw = true; }
+
+    var s = 0;
+    for (var i = 0; i < 2; i++) {
+        if (!would_throw) {
+            var x = 1;
+            Array(1);
+            x = 2;
+            x = t + t; // May throw if too large
+            s += x.length;
+        }
+    }
+    return s;
+}
+
+var x = '$';
+for (var i = 0; i < 32; ++i) {
+    with({}){}
+    f(x);
+    try { x = x + x; } catch (e) { }
+}
--- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js
+++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js
@@ -1378,18 +1378,18 @@ for (j = 100 - max; j < 100; j++) {
     rimul_object(i);
     rdiv_number(i);
     rdiv_float(i);
     rdiv_object(i);
     rmod_number(i);
     rmod_object(i);
     rnot_number(i);
     rnot_object(i);
-    rconcat_string(i);
-    rconcat_number(i);
+ // rconcat_string(i);  // Disabled by Bug 1365782
+ // rconcat_number(i);  // Disabled by Bug 1365782
     rstring_length(i);
     rarguments_length_1(i);
     rarguments_length_3(i, 0, 1);
     rinline_arguments_length_1(i);
     rinline_arguments_length_3(i, 0, 1);
     rfloor_number(i);
     rfloor_object(i);
     rceil_number(i);
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1995,16 +1995,17 @@ jit::FinishBailoutToBaseline(BaselineBai
       case Bailout_NonObjectInput:
       case Bailout_NonStringInput:
       case Bailout_NonSymbolInput:
       case Bailout_UnexpectedSimdInput:
       case Bailout_NonSharedTypedArrayInput:
       case Bailout_Debugger:
       case Bailout_UninitializedThis:
       case Bailout_BadDerivedConstructorReturn:
+      case Bailout_NotPure:
         // Do nothing.
         break;
 
       case Bailout_FirstExecution:
         // Do not return directly, as this was not frequent in the first place,
         // thus rely on the check for frequent bailouts to recompile the current
         // script.
         break;
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -7431,31 +7431,39 @@ CodeGenerator::visitIsNullOrLikeUndefine
         Register scratch = ToRegister(lir->temp());
         testObjectEmulatesUndefined(input, ifTrueLabel, ifFalseLabel, scratch, ool);
     } else {
         MOZ_ASSERT(lhsType == MIRType::ObjectOrNull);
         testZeroEmitBranch(Assembler::Equal, input, ifTrue, ifFalse);
     }
 }
 
-typedef JSString* (*ConcatStringsFn)(JSContext*, HandleString, HandleString);
-static const VMFunction ConcatStringsInfo =
-    FunctionInfo<ConcatStringsFn>(ConcatStrings<CanGC>, "ConcatStrings");
+typedef bool (*ConcatStringsPureFn)(JSContext*, JSString*, JSString*, JSString**);
+static const VMFunction ConcatStringsPureInfo =
+    FunctionInfo<ConcatStringsPureFn>(ConcatStringsPure, "ConcatStringsPure");
 
 void
 CodeGenerator::emitConcat(LInstruction* lir, Register lhs, Register rhs, Register output)
 {
-    OutOfLineCode* ool = oolCallVM(ConcatStringsInfo, lir, ArgList(lhs, rhs),
+    OutOfLineCode* ool = oolCallVM(ConcatStringsPureInfo, lir, ArgList(lhs, rhs),
                                    StoreRegisterTo(output));
 
+    Label done, bail;
     JitCode* stringConcatStub = gen->compartment->jitCompartment()->stringConcatStubNoBarrier();
     masm.call(stringConcatStub);
-    masm.branchTestPtr(Assembler::Zero, output, output, ool->entry());
-
+    masm.branchTestPtr(Assembler::NonZero, output, output, &done);
+
+    // If the concat would otherwise throw, we instead return nullptr and use
+    // this to signal a bailout. This allows MConcat to be movable.
+    masm.jump(ool->entry());
     masm.bind(ool->rejoin());
+    masm.branchTestPtr(Assembler::Zero, output, output, &bail);
+
+    masm.bind(&done);
+    bailoutFrom(&bail, lir->snapshot());
 }
 
 void
 CodeGenerator::visitConcat(LConcat* lir)
 {
     Register lhs = ToRegister(lir->lhs());
     Register rhs = ToRegister(lir->rhs());
 
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -116,16 +116,21 @@ enum BailoutKind
     Bailout_Debugger,
 
     // |this| used uninitialized in a derived constructor
     Bailout_UninitializedThis,
 
     // Derived constructors must return object or undefined
     Bailout_BadDerivedConstructorReturn,
 
+    // Operation would throw or GC to complete. Bailout and retry the operation
+    // in baseline. Allows instructions to be hoisted while exceptions throw
+    // from correct location.
+    Bailout_NotPure,
+
     // We hit this code for the first time.
     Bailout_FirstExecution,
 
     // END Normal bailouts
 
     // Bailouts caused by invalid assumptions based on Baseline code.
     // Causes immediate invalidation.
 
@@ -218,16 +223,18 @@ BailoutKindString(BailoutKind kind)
       case Bailout_NonSharedTypedArrayInput:
         return "Bailout_NonSharedTypedArrayInput";
       case Bailout_Debugger:
         return "Bailout_Debugger";
       case Bailout_UninitializedThis:
         return "Bailout_UninitializedThis";
       case Bailout_BadDerivedConstructorReturn:
         return "Bailout_BadDerivedConstructorReturn";
+      case Bailout_NotPure:
+        return "Bailout_NotPure";
       case Bailout_FirstExecution:
         return "Bailout_FirstExecution";
 
       // Bailouts caused by invalid assumptions.
       case Bailout_OverflowInvalidate:
         return "Bailout_OverflowInvalidate";
       case Bailout_NonStringInputInvalidate:
         return "Bailout_NonStringInputInvalidate";
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -1962,16 +1962,17 @@ LIRGenerator::visitConcat(MConcat* ins)
 
     LConcat* lir = new(alloc()) LConcat(useFixedAtStart(lhs, CallTempReg0),
                                         useFixedAtStart(rhs, CallTempReg1),
                                         tempFixed(CallTempReg0),
                                         tempFixed(CallTempReg1),
                                         tempFixed(CallTempReg2),
                                         tempFixed(CallTempReg3),
                                         tempFixed(CallTempReg4));
+    assignSnapshot(lir, Bailout_NotPure);
     defineFixed(lir, ins, LAllocation(AnyRegister(CallTempReg5)));
     assignSafepoint(lir, ins);
 }
 
 void
 LIRGenerator::visitCharCodeAt(MCharCodeAt* ins)
 {
     MDefinition* str = ins->getOperand(0);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -7324,16 +7324,21 @@ class MConcat
     MConcat(MDefinition* left, MDefinition* right)
       : MBinaryInstruction(left, right)
     {
         // At least one input should be definitely string
         MOZ_ASSERT(left->type() == MIRType::String || right->type() == MIRType::String);
 
         setMovable();
         setResultType(MIRType::String);
+
+        // StringConcat throws a catchable exception. Even though we bail to
+        // baseline in that case, we must set Guard to ensure the bailout
+        // happens.
+        setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(Concat)
     TRIVIAL_NEW_WRAPPERS
 
     MDefinition* foldsTo(TempAllocator& alloc) override;
     bool congruentTo(const MDefinition* ins) const override {
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1785,10 +1785,25 @@ HasOwnNativeDataProperty(JSContext* cx, 
 
 JSString*
 TypeOfObject(JSObject* obj, JSRuntime* rt)
 {
     JSType type = js::TypeOfObject(obj);
     return TypeName(type, *rt->commonNames);
 }
 
+bool
+ConcatStringsPure(JSContext* cx, JSString* lhs, JSString* rhs, JSString** res)
+{
+    JS::AutoCheckCannotGC nogc;
+
+    // ConcatStrings without GC or throwing. If this fails, we set result to
+    // nullptr and let caller do a bailout. Return true to indicate no exception
+    // thrown.
+    *res = ConcatStrings<NoGC>(cx, lhs, rhs);
+
+    MOZ_ASSERT(!cx->isExceptionPending());
+    return true;
+}
+
+
 } // namespace jit
 } // namespace js
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -438,16 +438,17 @@ template <class T> struct TypeToRootType
 
 template <class> struct OutParamToDataType { static const DataType result = Type_Void; };
 template <> struct OutParamToDataType<Value*> { static const DataType result = Type_Value; };
 template <> struct OutParamToDataType<int*> { static const DataType result = Type_Int32; };
 template <> struct OutParamToDataType<uint32_t*> { static const DataType result = Type_Int32; };
 template <> struct OutParamToDataType<uint8_t**> { static const DataType result = Type_Pointer; };
 template <> struct OutParamToDataType<bool*> { static const DataType result = Type_Bool; };
 template <> struct OutParamToDataType<double*> { static const DataType result = Type_Double; };
+template <> struct OutParamToDataType<JSString**> { static const DataType result = Type_Pointer; };
 template <> struct OutParamToDataType<MutableHandleValue> { static const DataType result = Type_Handle; };
 template <> struct OutParamToDataType<MutableHandleObject> { static const DataType result = Type_Handle; };
 template <> struct OutParamToDataType<MutableHandleString> { static const DataType result = Type_Handle; };
 
 template <class> struct OutParamToRootType {
     static const VMFunction::RootType result = VMFunction::RootNone;
 };
 template <> struct OutParamToRootType<MutableHandleValue> {
@@ -864,12 +865,15 @@ bool
 SetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* val);
 
 bool
 ObjectHasGetterSetter(JSContext* cx, JSObject* obj, Shape* propShape);
 
 JSString*
 TypeOfObject(JSObject* obj, JSRuntime* rt);
 
+bool
+ConcatStringsPure(JSContext* cx, JSString* lhs, JSString* rhs, JSString** res);
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_VMFunctions_h */
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/masking/mask-composite-1d.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Masking: mask-composite: compose vector image</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-mask-composite">
+    <link rel="match" href="mask-composite-1-ref.html">
+    <meta name="assert" content="Test checks that whether mask-composite is filled automatically according to the number of mask layers.">
+    <style type="text/css">
+      div {
+        background-color: blue;
+        position: absolute;
+        margin: 0px;
+        padding: 0px;
+        width: 100px;
+        height: 100px;
+        top: 10px;
+      }
+
+      div.add {
+        left: 10px;
+        mask-composite: add;
+        mask-image: url(support/blue-100x50-transparent-100x50.svg),
+                    url(support/blue-100x50-transparent-100x50.svg),
+                    url(support/blue-100x50-transparent-100x50.svg);
+      }
+
+      div.intersect {
+        left: 120px;
+        mask-composite: intersect;
+        mask-image: url(support/blue-100x50-transparent-100x50.svg),
+                    url(support/transparent-100x50-blue-100x50.svg),
+                    linear-gradient(rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 100%);
+      }
+
+      div.subtract {
+        left: 230px;
+        mask-composite: subtract;
+        mask-image: url(support/blue-100x50-transparent-100x50.svg),
+                    url(support/blue-100x50-transparent-100x50.svg),
+                    url(support/blue-100x50-transparent-100x50.svg);
+      }
+
+      div.exclude {
+        left: 340px;
+        mask-composite: exclude;
+        mask-image: url(support/transparent-100x50-blue-100x50.svg),
+                    linear-gradient(rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 100%),
+                    url(support/blue-100x50-transparent-100x50.svg);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="add"></div>
+    <div class="intersect"></div>
+    <div class="subtract"></div>
+    <div class="exclude"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/masking/mask-mode-d.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Masking: mask-mode with raster image</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css-masking-1/#propdef-mask-mode">
+    <link rel="match" href="mask-mode-ref.html">
+    <meta name="assert" content="Test checks that whether mask-mode is filled automatically according to the number of mask layers.">
+    <style type="text/css">
+      div {
+        background-color: blue;
+        position: absolute;
+        margin: 0px;
+        padding: 0px;
+        width: 100px;
+        height: 100px;
+        top: 10px;
+      }
+
+      div.auto {
+        left: 10px;
+        mask-mode: match-source;
+        mask-image: linear-gradient(black 0%, black 100%), linear-gradient(blue 0%, blue 100%);
+      }
+
+      div.alpha {
+        left: 120px;
+        mask-mode: alpha alpha;
+        mask-image: linear-gradient(black 0%, black 100%), linear-gradient(blue 0%, blue 100%);
+      }
+
+      div.luminance1 {
+        left: 230px;
+        mask-mode: luminance;
+        mask-image: linear-gradient(black 0%, black 100%), linear-gradient(blue 0%, blue 100%);
+      }
+
+      div.luminance2 {
+        left: 340px;
+        mask-mode: luminance;
+        mask-image: linear-gradient(black 0%, black 100%), linear-gradient(red 0%, red 100%);
+      }
+
+      div.luminance3 {
+        left: 450px;
+        mask-mode: luminance;
+        mask-image: linear-gradient(black 0%, black 100%), linear-gradient(lime 0%, lime 100%);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="auto"></div>
+    <div class="alpha"></div>
+    <div class="luminance1"></div>
+    <div class="luminance2"></div>
+    <div class="luminance3"></div>
+  </body>
+</html>
\ No newline at end of file
--- a/layout/reftests/w3c-css/submitted/masking/reftest.list
+++ b/layout/reftests/w3c-css/submitted/masking/reftest.list
@@ -1,22 +1,24 @@
 # For those test items with failure type fuzzy-if added, please refer to bug 1231643#c10.
 
 # mask-composite test cases
 == mask-composite-1a.html mask-composite-1-ref.html
 fuzzy-if(skiaContent,64,200) == mask-composite-1b.html mask-composite-1-ref.html
 == mask-composite-1c.html mask-composite-1-ref.html
+== mask-composite-1d.html mask-composite-1-ref.html
 fuzzy-if(skiaContent||winWidget,1,5000) == mask-composite-2a.html mask-composite-2-ref.html
 fuzzy-if(skiaContent||winWidget,64,5200) == mask-composite-2b.html mask-composite-2-ref.html
 == mask-composite-2c.html mask-composite-2-ref.html
 
 # mask-mode test cases
 fuzzy-if(skiaContent,1,30000) == mask-mode-a.html mask-mode-ref.html
 fuzzy-if(skiaContent,1,30000) == mask-mode-b.html mask-mode-ref.html
 fuzzy-if(skiaContent,1,30000) == mask-mode-c.html mask-mode-ref.html
+fuzzy-if(skiaContent,1,30000) == mask-mode-d.html mask-mode-ref.html
 fuzzy-if(skiaContent,1,30000) == mask-mode-to-mask-type.html mask-mode-to-mask-type-ref.html
 
 # mask-image test cases
 == mask-image-1a.html mask-image-1-ref.html
 == mask-image-1b.html mask-image-1-ref.html
 == mask-image-1c.html mask-image-1-ref.html
 == mask-image-1d.html mask-image-1-ref.html
 fuzzy-if(skiaContent||winWidget,1,20000) == mask-image-2.html mask-image-2-ref.html
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7291,16 +7291,22 @@ nsRuleNode::FillAllBackgroundLists(nsSty
                                   &Position::mXPosition,
                                   aImage.mPositionXCount, fillCount);
   FillImageLayerPositionCoordList(aImage.mLayers,
                                   &Position::mYPosition,
                                   aImage.mPositionYCount, fillCount);
   FillImageLayerList(aImage.mLayers,
                      &nsStyleImageLayers::Layer::mSize,
                      aImage.mSizeCount, fillCount);
+  FillImageLayerList(aImage.mLayers,
+                     &nsStyleImageLayers::Layer::mMaskMode,
+                     aImage.mMaskModeCount, fillCount);
+  FillImageLayerList(aImage.mLayers,
+                     &nsStyleImageLayers::Layer::mComposite,
+                     aImage.mCompositeCount, fillCount);
 }
 
 const void*
 nsRuleNode::ComputeBackgroundData(void* aStartStruct,
                                   const nsRuleData* aRuleData,
                                   nsStyleContext* aContext,
                                   nsRuleNode* aHighestNode,
                                   const RuleDetail aRuleDetail,
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -404,16 +404,22 @@ public class CustomTabsActivity extends 
             final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
             final ResolveInfo info = getPackageManager()
                     .resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY);
             final String name = info.loadLabel(getPackageManager()).toString();
             openItem.setTitle(getString(R.string.custom_tabs_menu_item_open_in, name));
         }
 
         menuItemControl = geckoMenu.findItem(R.id.custom_tabs_menu_control);
+        // on some configurations(ie. Android 5.1.1 + Nexus 5), no idea why the state not be enabled
+        // if the Drawable is a LevelListDrawable, then the icon color is incorrect.
+        final Drawable icon = menuItemControl.getIcon();
+        if (icon != null && !icon.isStateful()) {
+            icon.setState(new int[]{android.R.attr.state_enabled});
+        }
 
         geckoMenu.addFooterView(
                 getLayoutInflater().inflate(R.layout.customtabs_options_menu_footer, geckoMenu, false),
                 null,
                 false);
 
         return popupMenu;
     }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -192,17 +192,17 @@ pref("dom.requestIdleCallback.enabled", 
 // Whether the Gamepad API is enabled
 pref("dom.gamepad.enabled", true);
 pref("dom.gamepad.test.enabled", false);
 #ifdef RELEASE_OR_BETA
 pref("dom.gamepad.non_standard_events.enabled", false);
 #else
 pref("dom.gamepad.non_standard_events.enabled", true);
 #endif
-pref("dom.gamepad.extensions.enabled", false);
+pref("dom.gamepad.extensions.enabled", true);
 
 // If this is true, TextEventDispatcher dispatches keydown and keyup events
 // even during composition (keypress events are never fired during composition
 // even if this is true).
 pref("dom.keyboardevent.dispatch_during_composition", false);
 
 // Whether to run add-on code in different compartments from browser code. This
 // causes a separate compartment for each (addon, global) combination, which may
--- a/python/mozbuild/mozpack/packager/formats.py
+++ b/python/mozbuild/mozpack/packager/formats.py
@@ -6,16 +6,17 @@ from __future__ import absolute_import
 
 from mozpack.chrome.manifest import (
     Manifest,
     ManifestEntryWithRelPath,
     ManifestInterfaces,
     ManifestChrome,
     ManifestBinaryComponent,
     ManifestResource,
+    ManifestMultiContent,
 )
 from mozpack.errors import errors
 from urlparse import urlparse
 import mozpack.path as mozpath
 from mozpack.files import (
     ManifestFile,
     XPTFile,
 )
@@ -152,21 +153,28 @@ class FlatSubFormatter(object):
                 relbase = mozpath.basename(entry.base)
                 relpath = mozpath.join(relbase,
                                             mozpath.basename(path))
                 self.add_manifest(Manifest(parent, relpath))
             self.copier.add(path, ManifestFile(entry.base))
 
         if isinstance(entry, ManifestChrome):
             data = self._chrome_db.setdefault(entry.name, {})
-            entries = data.setdefault(entry.type, [])
+            if isinstance(entry, ManifestMultiContent):
+                entries = data.setdefault(entry.type, {}) \
+                              .setdefault(entry.id, [])
+            else:
+                entries = data.setdefault(entry.type, [])
             for e in entries:
                 # Ideally, we'd actually check whether entry.flags are more
                 # specific than e.flags, but in practice the following test
                 # is enough for now.
+                if entry == e:
+                    errors.warn('"%s" is duplicated. Skipping.' % entry)
+                    return
                 if not entry.flags or e.flags and entry.flags == e.flags:
                     errors.fatal('"%s" overrides "%s"' % (entry, e))
             entries.append(entry)
 
         self.copier[path].add(entry)
 
     def add_interfaces(self, path, content):
         # Interfaces in the same directory are all linked together in an
--- a/python/mozbuild/mozpack/test/test_packager_formats.py
+++ b/python/mozbuild/mozpack/test/test_packager_formats.py
@@ -14,29 +14,32 @@ from mozpack.files import (
     GeneratedFile,
     ManifestFile,
 )
 from mozpack.chrome.manifest import (
     ManifestContent,
     ManifestComponent,
     ManifestResource,
     ManifestBinaryComponent,
+    ManifestSkin,
+    ManifestLocale,
 )
 from mozpack.errors import (
     errors,
     ErrorMessage,
 )
 from mozpack.test.test_files import (
     MockDest,
     foo_xpt,
     foo2_xpt,
     bar_xpt,
     read_interfaces,
 )
 import mozpack.path as mozpath
+from test_errors import TestErrors
 
 
 CONTENTS = {
     'bases': {
         # base_path: is_addon?
         '': False,
         'app': False,
         'addon0': 'unpacked',
@@ -299,17 +302,17 @@ def get_contents(registry, read_all=Fals
             result[k] = get_contents(v)
         elif isinstance(v, ManifestFile) or read_all:
             result[k] = v.open().read().splitlines()
         else:
             result[k] = v
     return result
 
 
-class TestFormatters(unittest.TestCase):
+class TestFormatters(TestErrors, unittest.TestCase):
     maxDiff = None
 
     def test_bases(self):
         formatter = FlatFormatter(FileRegistry())
         formatter.add_base('')
         formatter.add_base('browser')
         formatter.add_base('addon0', addon=True)
         self.assertEqual(formatter._get_base('platform.ini'),
@@ -461,10 +464,46 @@ class TestFormatters(unittest.TestCase):
         self.assertEqual(e.exception.message,
             'Error: "content bar bar/unix" overrides '
             '"content bar bar/win os=WINNT"')
 
         # Adding something more specific still works.
         f.add_manifest(ManifestContent('chrome', 'bar', 'bar/win',
                                        'os=WINNT osversion>=7.0'))
 
+        # Variations of skin/locales are allowed.
+        f.add_manifest(ManifestSkin('chrome', 'foo', 'classic/1.0',
+                                    'foo/skin/classic/'))
+        f.add_manifest(ManifestSkin('chrome', 'foo', 'modern/1.0',
+                                    'foo/skin/modern/'))
+
+        f.add_manifest(ManifestLocale('chrome', 'foo', 'en-US',
+                                    'foo/locale/en-US/'))
+        f.add_manifest(ManifestLocale('chrome', 'foo', 'ja-JP',
+                                    'foo/locale/ja-JP/'))
+
+        # But same-skin/locale still error out.
+        with self.assertRaises(ErrorMessage) as e:
+            f.add_manifest(ManifestSkin('chrome', 'foo', 'classic/1.0',
+                                        'foo/skin/classic/foo'))
+
+        self.assertEqual(e.exception.message,
+            'Error: "skin foo classic/1.0 foo/skin/classic/foo" overrides '
+            '"skin foo classic/1.0 foo/skin/classic/"')
+
+        with self.assertRaises(ErrorMessage) as e:
+            f.add_manifest(ManifestLocale('chrome', 'foo', 'en-US',
+                                         'foo/locale/en-US/foo'))
+
+        self.assertEqual(e.exception.message,
+            'Error: "locale foo en-US foo/locale/en-US/foo" overrides '
+            '"locale foo en-US foo/locale/en-US/"')
+
+        # Duplicating existing manifest entries is not an error.
+        f.add_manifest(ManifestContent('chrome', 'foo', 'foo/unix'))
+
+        self.assertEqual(self.get_output(), [
+            'Warning: "content foo foo/unix" is duplicated. Skipping.',
+        ])
+
+
 if __name__ == '__main__':
     mozunit.main()
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -439,16 +439,17 @@ CertVerifier::SHA1ModeMoreRestrictiveTha
 
 static const unsigned int MIN_RSA_BITS = 2048;
 static const unsigned int MIN_RSA_BITS_WEAK = 1024;
 
 Result
 CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
                          Time time, void* pinArg, const char* hostname,
                  /*out*/ UniqueCERTCertList& builtChain,
+            /*optional*/ UniqueCERTCertList* peerCertChain,
             /*optional*/ const Flags flags,
             /*optional*/ const SECItem* stapledOCSPResponseSECItem,
             /*optional*/ const SECItem* sctsFromTLSSECItem,
             /*optional*/ const OriginAttributes& originAttributes,
         /*optional out*/ SECOidTag* evOidPolicy,
         /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
         /*optional out*/ KeySizeStatus* keySizeStatus,
         /*optional out*/ SHA1ModeResult* sha1ModeResult,
@@ -538,17 +539,18 @@ CertVerifier::VerifyCert(CERTCertificate
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
                                        originAttributes,
-                                       builtChain, nullptr, nullptr);
+                                       builtChain, peerCertChain, nullptr,
+                                       nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_clientAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
@@ -612,18 +614,18 @@ CertVerifier::VerifyCert(CERTCertificate
 
         NSSCertDBTrustDomain
           trustDomain(trustSSL, evOCSPFetching,
                       mOCSPCache, pinArg, ocspGETConfig,
                       mOCSPTimeoutSoft, mOCSPTimeoutHard,
                       mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS,
                       ValidityCheckingMode::CheckForEV,
                       sha1ModeConfigurations[i], mNetscapeStepUpPolicy,
-                      originAttributes, builtChain, pinningTelemetryInfo,
-                      hostname);
+                      originAttributes, builtChain, peerCertChain,
+                      pinningTelemetryInfo, hostname);
         rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                           KeyUsage::digitalSignature,// (EC)DHE
                                           KeyUsage::keyEncipherment, // RSA
                                           KeyUsage::keyAgreement,    // (EC)DH
                                           KeyPurposeId::id_kp_serverAuth,
                                           evPolicy, stapledOCSPResponse,
                                           ocspStaplingStatus);
         if (rv == Success &&
@@ -702,17 +704,18 @@ CertVerifier::VerifyCert(CERTCertificate
                                            mOCSPCache, pinArg, ocspGETConfig,
                                            mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                            mCertShortLifetimeInDays,
                                            mPinningMode, keySizeOptions[i],
                                            ValidityCheckingMode::CheckingOff,
                                            sha1ModeConfigurations[j],
                                            mNetscapeStepUpPolicy,
                                            originAttributes, builtChain,
-                                           pinningTelemetryInfo, hostname);
+                                           peerCertChain, pinningTelemetryInfo,
+                                           hostname);
           rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                             KeyUsage::digitalSignature,//(EC)DHE
                                             KeyUsage::keyEncipherment,//RSA
                                             KeyUsage::keyAgreement,//(EC)DH
                                             KeyPurposeId::id_kp_serverAuth,
                                             CertPolicyId::anyPolicy,
                                             stapledOCSPResponse,
                                             ocspStaplingStatus);
@@ -765,36 +768,36 @@ CertVerifier::VerifyCert(CERTCertificate
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed, mNetscapeStepUpPolicy,
-                                       originAttributes, builtChain, nullptr,
-                                       nullptr);
+                                       originAttributes, builtChain,
+                                       peerCertChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign,
                           KeyPurposeId::id_kp_serverAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
-                                       originAttributes, builtChain, nullptr,
-                                       nullptr);
+                                       originAttributes, builtChain,
+                                       peerCertChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
                             EndEntityOrCA::MustBeEndEntity,
@@ -812,18 +815,18 @@ CertVerifier::VerifyCert(CERTCertificate
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
-                                       originAttributes, builtChain, nullptr,
-                                       nullptr);
+                                       originAttributes, builtChain,
+                                       peerCertChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::keyEncipherment, // RSA
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
                             EndEntityOrCA::MustBeEndEntity,
@@ -838,18 +841,18 @@ CertVerifier::VerifyCert(CERTCertificate
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
-                                       originAttributes, builtChain, nullptr,
-                                       nullptr);
+                                       originAttributes, builtChain,
+                                       peerCertChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_codeSigning,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
@@ -873,32 +876,32 @@ CertVerifier::VerifyCert(CERTCertificate
 
       NSSCertDBTrustDomain sslTrust(trustSSL, defaultOCSPFetching, mOCSPCache,
                                     pinArg, ocspGETConfig, mOCSPTimeoutSoft,
                                     mOCSPTimeoutHard, mCertShortLifetimeInDays,
                                     pinningDisabled, MIN_RSA_BITS_WEAK,
                                     ValidityCheckingMode::CheckingOff,
                                     SHA1Mode::Allowed,
                                     NetscapeStepUpPolicy::NeverMatch,
-                                    originAttributes, builtChain, nullptr,
-                                    nullptr);
+                                    originAttributes, builtChain, peerCertChain,
+                                    nullptr, nullptr);
       rv = BuildCertChain(sslTrust, certDER, time, endEntityOrCA,
                           keyUsage, eku, CertPolicyId::anyPolicy,
                           stapledOCSPResponse);
       if (rv == Result::ERROR_UNKNOWN_ISSUER) {
         NSSCertDBTrustDomain emailTrust(trustEmail, defaultOCSPFetching,
                                         mOCSPCache, pinArg, ocspGETConfig,
                                         mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                         mCertShortLifetimeInDays,
                                         pinningDisabled, MIN_RSA_BITS_WEAK,
                                         ValidityCheckingMode::CheckingOff,
                                         SHA1Mode::Allowed,
                                         NetscapeStepUpPolicy::NeverMatch,
-                                        originAttributes, builtChain, nullptr,
-                                        nullptr);
+                                        originAttributes, builtChain,
+                                        peerCertChain, nullptr, nullptr);
         rv = BuildCertChain(emailTrust, certDER, time, endEntityOrCA,
                             keyUsage, eku, CertPolicyId::anyPolicy,
                             stapledOCSPResponse);
         if (rv == Result::ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
                                                   defaultOCSPFetching,
                                                   mOCSPCache, pinArg,
                                                   ocspGETConfig,
@@ -906,17 +909,18 @@ CertVerifier::VerifyCert(CERTCertificate
                                                   mOCSPTimeoutHard,
                                                   mCertShortLifetimeInDays,
                                                   pinningDisabled,
                                                   MIN_RSA_BITS_WEAK,
                                                   ValidityCheckingMode::CheckingOff,
                                                   SHA1Mode::Allowed,
                                                   NetscapeStepUpPolicy::NeverMatch,
                                                   originAttributes, builtChain,
-                                                  nullptr, nullptr);
+                                                  peerCertChain, nullptr,
+                                                  nullptr);
           rv = BuildCertChain(objectSigningTrust, certDER, time,
                               endEntityOrCA, keyUsage, eku,
                               CertPolicyId::anyPolicy, stapledOCSPResponse);
         }
       }
 
       break;
     }
@@ -935,16 +939,17 @@ CertVerifier::VerifyCert(CERTCertificate
 Result
 CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert,
                      /*optional*/ const SECItem* stapledOCSPResponse,
                      /*optional*/ const SECItem* sctsFromTLS,
                                   Time time,
                      /*optional*/ void* pinarg,
                                   const char* hostname,
                           /*out*/ UniqueCERTCertList& builtChain,
+                     /*optional*/ UniqueCERTCertList* peerCertChain,
                      /*optional*/ bool saveIntermediatesInPermanentDatabase,
                      /*optional*/ Flags flags,
                      /*optional*/ const OriginAttributes& originAttributes,
                  /*optional out*/ SECOidTag* evOidPolicy,
                  /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
                  /*optional out*/ KeySizeStatus* keySizeStatus,
                  /*optional out*/ SHA1ModeResult* sha1ModeResult,
                  /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
@@ -961,17 +966,17 @@ CertVerifier::VerifySSLServerCert(const 
 
   if (!hostname || !hostname[0]) {
     return Result::ERROR_BAD_CERT_DOMAIN;
   }
 
   // CreateCertErrorRunnable assumes that CheckCertHostname is only called
   // if VerifyCert succeeded.
   Result rv = VerifyCert(peerCert.get(), certificateUsageSSLServer, time,
-                         pinarg, hostname, builtChain, flags,
+                         pinarg, hostname, builtChain, peerCertChain, flags,
                          stapledOCSPResponse, sctsFromTLS, originAttributes,
                          evOidPolicy, ocspStaplingStatus, keySizeStatus,
                          sha1ModeResult, pinningTelemetryInfo, ctInfo);
   if (rv != Success) {
     return rv;
   }
 
   Input peerCertInput;
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -110,26 +110,32 @@ public:
   enum OCSPStaplingStatus {
     OCSP_STAPLING_NEVER_CHECKED = 0,
     OCSP_STAPLING_GOOD = 1,
     OCSP_STAPLING_NONE = 2,
     OCSP_STAPLING_EXPIRED = 3,
     OCSP_STAPLING_INVALID = 4,
   };
 
+  // As an optimization, a pointer to the certificate chain sent by the peer
+  // may be specified as peerCertChain. This can prevent NSSCertDBTrustDomain
+  // from calling CERT_CreateSubjectCertList to find potential issuers, which
+  // can be expensive.
+  //
   // *evOidPolicy == SEC_OID_UNKNOWN means the cert is NOT EV
   // Only one usage per verification is supported.
   mozilla::pkix::Result VerifyCert(
                     CERTCertificate* cert,
                     SECCertificateUsage usage,
                     mozilla::pkix::Time time,
                     void* pinArg,
                     const char* hostname,
             /*out*/ UniqueCERTCertList& builtChain,
-                    Flags flags = 0,
+    /*optional in*/ UniqueCERTCertList* peerCertChain = nullptr,
+    /*optional in*/ Flags flags = 0,
     /*optional in*/ const SECItem* stapledOCSPResponse = nullptr,
     /*optional in*/ const SECItem* sctsFromTLS = nullptr,
     /*optional in*/ const OriginAttributes& originAttributes =
                       OriginAttributes(),
    /*optional out*/ SECOidTag* evOidPolicy = nullptr,
    /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
    /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
    /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
@@ -139,16 +145,17 @@ public:
   mozilla::pkix::Result VerifySSLServerCert(
                     const UniqueCERTCertificate& peerCert,
        /*optional*/ const SECItem* stapledOCSPResponse,
        /*optional*/ const SECItem* sctsFromTLS,
                     mozilla::pkix::Time time,
        /*optional*/ void* pinarg,
                     const char* hostname,
             /*out*/ UniqueCERTCertList& builtChain,
+       /*optional*/ UniqueCERTCertList* peerCertChain = nullptr,
        /*optional*/ bool saveIntermediatesInPermanentDatabase = false,
        /*optional*/ Flags flags = 0,
        /*optional*/ const OriginAttributes& originAttributes =
                       OriginAttributes(),
    /*optional out*/ SECOidTag* evOidPolicy = nullptr,
    /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
    /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
    /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -54,16 +54,17 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
                                            uint32_t certShortLifetimeInDays,
                                            CertVerifier::PinningMode pinningMode,
                                            unsigned int minRSABits,
                                            ValidityCheckingMode validityCheckingMode,
                                            CertVerifier::SHA1Mode sha1Mode,
                                            NetscapeStepUpPolicy netscapeStepUpPolicy,
                                            const OriginAttributes& originAttributes,
                                            UniqueCERTCertList& builtChain,
+                              /*optional*/ UniqueCERTCertList* peerCertChain,
                               /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo,
                               /*optional*/ const char* hostname)
   : mCertDBTrustType(certDBTrustType)
   , mOCSPFetching(ocspFetching)
   , mOCSPCache(ocspCache)
   , mPinArg(pinArg)
   , mOCSPGetConfig(ocspGETConfig)
   , mOCSPTimeoutSoft(ocspTimeoutSoft)
@@ -71,16 +72,17 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mMinRSABits(minRSABits)
   , mValidityCheckingMode(validityCheckingMode)
   , mSHA1Mode(sha1Mode)
   , mNetscapeStepUpPolicy(netscapeStepUpPolicy)
   , mOriginAttributes(originAttributes)
   , mBuiltChain(builtChain)
+  , mPeerCertChain(peerCertChain)
   , mPinningTelemetryInfo(pinningTelemetryInfo)
   , mHostname(hostname)
   , mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
   , mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED)
   , mSCTListFromCertificate()
   , mSCTListFromOCSPStapling()
 {
 }
@@ -135,30 +137,113 @@ FindIssuerInner(const UniqueCERTCertList
     if (!keepGoing) {
       break;
     }
   }
 
   return Success;
 }
 
+// Remove from newCandidates any CERTCertificates in alreadyTried.
+// alreadyTried is likely to be small or empty.
+static void
+RemoveCandidatesAlreadyTried(UniqueCERTCertList& newCandidates,
+                             const UniqueCERTCertList& alreadyTried)
+{
+  for (const CERTCertListNode* triedNode = CERT_LIST_HEAD(alreadyTried);
+       !CERT_LIST_END(triedNode, alreadyTried);
+       triedNode = CERT_LIST_NEXT(triedNode)) {
+    CERTCertListNode* newNode = CERT_LIST_HEAD(newCandidates);
+    while (!CERT_LIST_END(newNode, newCandidates)) {
+      CERTCertListNode* savedNode = CERT_LIST_NEXT(newNode);
+      if (CERT_CompareCerts(triedNode->cert, newNode->cert)) {
+        CERT_RemoveCertListNode(newNode);
+      }
+      newNode = savedNode;
+    }
+  }
+}
+
+// Add to matchingCandidates any CERTCertificates from candidatesIn that have a
+// DER-encoded subject name equal to the given subject name.
+static Result
+AddMatchingCandidates(UniqueCERTCertList& matchingCandidates,
+                      const UniqueCERTCertList& candidatesIn,
+                      Input subjectName)
+{
+  for (const CERTCertListNode* node = CERT_LIST_HEAD(candidatesIn);
+       !CERT_LIST_END(node, candidatesIn); node = CERT_LIST_NEXT(node)) {
+    Input candidateSubjectName;
+    Result rv = candidateSubjectName.Init(node->cert->derSubject.data,
+                                          node->cert->derSubject.len);
+    if (rv != Success) {
+      continue; // probably just too big - continue processing other candidates
+    }
+    if (InputsAreEqual(candidateSubjectName, subjectName)) {
+      UniqueCERTCertificate certDuplicate(CERT_DupCertificate(node->cert));
+      if (!certDuplicate) {
+        return Result::FATAL_ERROR_NO_MEMORY;
+      }
+      SECStatus srv = CERT_AddCertToListTail(matchingCandidates.get(),
+                                             certDuplicate.get());
+      if (srv != SECSuccess) {
+        return MapPRErrorCodeToResult(PR_GetError());
+      }
+      // matchingCandidates now owns certDuplicate
+      Unused << certDuplicate.release();
+    }
+  }
+  return Success;
+}
+
 Result
 NSSCertDBTrustDomain::FindIssuer(Input encodedIssuerName,
                                  IssuerChecker& checker, Time)
 {
+  // If the peer certificate chain was specified, try to use it before falling
+  // back to CERT_CreateSubjectCertList.
+  bool keepGoing;
+  UniqueCERTCertList peerCertChainCandidates(CERT_NewCertList());
+  if (!peerCertChainCandidates) {
+    return Result::FATAL_ERROR_NO_MEMORY;
+  }
+  if (mPeerCertChain) {
+    // Build a candidate list that consists only of certificates with a subject
+    // matching the issuer we're looking for.
+    Result rv = AddMatchingCandidates(peerCertChainCandidates, *mPeerCertChain,
+                                      encodedIssuerName);
+    if (rv != Success) {
+      return rv;
+    }
+    rv = FindIssuerInner(peerCertChainCandidates, true, encodedIssuerName,
+                         checker, keepGoing);
+    if (rv != Success) {
+      return rv;
+    }
+    if (keepGoing) {
+      rv = FindIssuerInner(peerCertChainCandidates, false, encodedIssuerName,
+                           checker, keepGoing);
+      if (rv != Success) {
+        return rv;
+      }
+    }
+    if (!keepGoing) {
+      return Success;
+    }
+  }
   // TODO: NSS seems to be ambiguous between "no potential issuers found" and
   // "there was an error trying to retrieve the potential issuers."
   SECItem encodedIssuerNameItem = UnsafeMapInputToSECItem(encodedIssuerName);
   UniqueCERTCertList
     candidates(CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
                                           &encodedIssuerNameItem, 0,
                                           false));
   if (candidates) {
+    RemoveCandidatesAlreadyTried(candidates, peerCertChainCandidates);
     // First, try all the root certs; then try all the non-root certs.
-    bool keepGoing;
     Result rv = FindIssuerInner(candidates, true, encodedIssuerName, checker,
                                 keepGoing);
     if (rv != Success) {
       return rv;
     }
     if (keepGoing) {
       rv = FindIssuerInner(candidates, false, encodedIssuerName, checker,
                            keepGoing);
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -83,16 +83,17 @@ public:
                        uint32_t certShortLifetimeInDays,
                        CertVerifier::PinningMode pinningMode,
                        unsigned int minRSABits,
                        ValidityCheckingMode validityCheckingMode,
                        CertVerifier::SHA1Mode sha1Mode,
                        NetscapeStepUpPolicy netscapeStepUpPolicy,
                        const OriginAttributes& originAttributes,
                        UniqueCERTCertList& builtChain,
+          /*optional*/ UniqueCERTCertList* peerCertChain = nullptr,
           /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
           /*optional*/ const char* hostname = nullptr);
 
   virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName,
                             IssuerChecker& checker,
                             mozilla::pkix::Time time) override;
 
   virtual Result GetCertTrust(mozilla::pkix::EndEntityOrCA endEntityOrCA,
@@ -192,16 +193,17 @@ private:
   const uint32_t mCertShortLifetimeInDays;
   CertVerifier::PinningMode mPinningMode;
   const unsigned int mMinRSABits;
   ValidityCheckingMode mValidityCheckingMode;
   CertVerifier::SHA1Mode mSHA1Mode;
   NetscapeStepUpPolicy mNetscapeStepUpPolicy;
   const OriginAttributes& mOriginAttributes;
   UniqueCERTCertList& mBuiltChain; // non-owning
+  UniqueCERTCertList* mPeerCertChain; // non-owning
   PinningTelemetryInfo* mPinningTelemetryInfo;
   const char* mHostname; // non-owning - only used for pinning checks
   nsCOMPtr<nsICertBlocklist> mCertBlocklist;
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
   // Certificate Transparency data extracted during certificate verification
   UniqueSECItem mSCTListFromCertificate;
   UniqueSECItem mSCTListFromOCSPStapling;
 };
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -1399,18 +1399,19 @@ AuthCertificate(CertVerifier& certVerifi
       !infoObject->SharedState().IsOCSPMustStapleEnabled()) {
     flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
   }
 
   Result rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse,
                                                sctsFromTLSExtension, time,
                                                infoObject,
                                                infoObject->GetHostNameRaw(),
-                                               certList, saveIntermediates,
-                                               flags, infoObject->
+                                               certList, &peerCertChain,
+                                               saveIntermediates, flags,
+                                               infoObject->
                                                       GetOriginAttributes(),
                                                &evOidPolicy,
                                                &ocspStaplingStatus,
                                                &keySizeStatus, &sha1ModeResult,
                                                &pinningTelemetryInfo,
                                                &certificateTransparencyInfo);
 
   uint32_t evStatus = (rv != Success) ? 0                   // 0 = Failure
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -1081,16 +1081,23 @@ DetermineEVAndCTStatusAndSetNewCert(RefP
   }
 
   UniqueCERTCertificate cert(SSL_PeerCertificate(fd));
   MOZ_ASSERT(cert, "SSL_PeerCertificate failed in TLS handshake callback?");
   if (!cert) {
     return;
   }
 
+  UniqueCERTCertList peerCertChain(SSL_PeerCertificateChain(fd));
+  MOZ_ASSERT(peerCertChain,
+             "SSL_PeerCertificateChain failed in TLS handshake callback?");
+  if (!peerCertChain) {
+    return;
+  }
+
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   MOZ_ASSERT(certVerifier,
              "Certificate verifier uninitialized in TLS handshake callback?");
   if (!certVerifier) {
     return;
   }
 
   // We don't own these pointers.
@@ -1122,16 +1129,17 @@ DetermineEVAndCTStatusAndSetNewCert(RefP
   mozilla::pkix::Result rv = certVerifier->VerifySSLServerCert(
     cert,
     stapledOCSPResponse,
     sctsFromTLSExtension,
     mozilla::pkix::Now(),
     infoObject,
     infoObject->GetHostNameRaw(),
     unusedBuiltChain,
+    &peerCertChain,
     saveIntermediates,
     flags,
     infoObject->GetOriginAttributes(),
     &evOidPolicy,
     nullptr, // OCSP stapling telemetry
     nullptr, // key size telemetry
     nullptr, // SHA-1 telemetry
     nullptr, // pinning telemetry
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -659,16 +659,17 @@ nsNSSCertificate::GetChain(nsIArray** _r
 
   UniqueCERTCertList nssChain;
   // We want to test all usages, but we start with server because most of the
   // time Firefox users care about server certs.
   if (certVerifier->VerifyCert(mCert.get(), certificateUsageSSLServer, now,
                                nullptr, /*XXX fixme*/
                                nullptr, /* hostname */
                                nssChain,
+                               nullptr, // no peerCertChain
                                CertVerifier::FLAG_LOCAL_ONLY)
         != mozilla::pkix::Success) {
     nssChain = nullptr;
     // keep going
   }
 
   // This is the whitelist of all non-SSLServer usages that are supported by
   // verifycert.
@@ -683,16 +684,17 @@ nsNSSCertificate::GetChain(nsIArray** _r
        usage = usage << 1) {
     if ((usage & otherUsagesToTest) == 0) {
       continue;
     }
     if (certVerifier->VerifyCert(mCert.get(), usage, now,
                                  nullptr, /*XXX fixme*/
                                  nullptr, /*hostname*/
                                  nssChain,
+                                 nullptr, // no peerCertChain
                                  CertVerifier::FLAG_LOCAL_ONLY)
           != mozilla::pkix::Success) {
       nssChain = nullptr;
       // keep going
     }
   }
 
   if (!nssChain) {
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -1404,26 +1404,28 @@ VerifyCertAtTime(nsIX509Cert* aCert,
   if (!aHostname.IsVoid() && aUsage == certificateUsageSSLServer) {
     result = certVerifier->VerifySSLServerCert(nssCert,
                                                nullptr, // stapledOCSPResponse
                                                nullptr, // sctsFromTLSExtension
                                                aTime,
                                                nullptr, // Assume no context
                                                flatHostname.get(),
                                                resultChain,
+                                               nullptr, // no peerCertChain
                                                false, // don't save intermediates
                                                aFlags,
                                                OriginAttributes(),
                                                &evOidPolicy);
   } else {
     result = certVerifier->VerifyCert(nssCert.get(), aUsage, aTime,
                                       nullptr, // Assume no context
                                       aHostname.IsVoid() ? nullptr
                                                          : flatHostname.get(),
                                       resultChain,
+                                      nullptr, // no peerCertChain
                                       aFlags,
                                       nullptr, // stapledOCSPResponse
                                       nullptr, // sctsFromTLSExtension
                                       OriginAttributes(),
                                       &evOidPolicy);
   }
 
   nsCOMPtr<nsIX509CertList> nssCertList;
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -456,16 +456,17 @@ nsNSSSocketInfo::IsAcceptableForHost(con
   mozilla::pkix::Result result =
     certVerifier->VerifySSLServerCert(nssCert,
                                       nullptr, // stapledOCSPResponse
                                       nullptr, // sctsFromTLSExtension
                                       mozilla::pkix::Now(),
                                       nullptr, // pinarg
                                       hostnameFlat.get(),
                                       unusedBuiltChain,
+                                      nullptr, // no peerCertChain
                                       false, // save intermediates
                                       flags);
   if (result != mozilla::pkix::Success) {
     return NS_OK;
   }
 
   // All tests pass
   *_retval = true;
--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -1073,16 +1073,17 @@ nsSiteSecurityService::ProcessPKPHeader(
   CertVerifier::Flags flags = CertVerifier::FLAG_LOCAL_ONLY |
                               CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
   if (certVerifier->VerifySSLServerCert(nssCert,
                                         nullptr, // stapledOCSPResponse
                                         nullptr, // sctsFromTLSExtension
                                         now, nullptr, // pinarg
                                         host.get(), // hostname
                                         certList,
+                                        nullptr, // no peerCertChain
                                         false, // don't store intermediates
                                         flags,
                                         aOriginAttributes)
         != mozilla::pkix::Success) {
     return NS_ERROR_FAILURE;
   }
 
   CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/bad_certs/ee-from-missing-intermediate.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+zCCAeWgAwIBAgIUf+ecjIpXJoXZQMMGs/O52mUg1LEwCwYJKoZIhvcNAQEL
+MB8xHTAbBgNVBAMMFE1pc3NpbmcgSW50ZXJtZWRpYXRlMCIYDzIwMTUxMTI4MDAw
+MDAwWhgPMjAxODAyMDUwMDAwMDBaMCcxJTAjBgNVBAMMHGVlLWZyb20tbWlzc2lu
+Zy1pbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
+iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr
+4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP
+8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OI
+Q+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ
+77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5J
+I/pyUcQx1QOs2hgKNe2NAgMBAAGjJzAlMCMGA1UdEQQcMBqCCWxvY2FsaG9zdIIN
+Ki5leGFtcGxlLmNvbTALBgkqhkiG9w0BAQsDggEBAD9EKfDooyLfO+uzKGsP2Xby
+RUm0ynRPttC8eqg+7hCuq583DtdZU7VUsPjIwckDMF2dtbx8wWl0vDcSSRQ84SGW
+9+y3bqRggVeqTbcdN+y0Q643CB71wRQQt/pRwga8vLkMkDYke9APDDak62vKEaTP
+MHSB5fT+m0AtE0RnN6OWZgpI++dlGpFsGsspUuO3z7qtBI7u1jBv4tIg8NeOINRf
+waik5YQ3CEGz34G/PkdvkAY95gd2/oNdV86J1D0IMzKv+JPQsFIDaw8GHXFHCs6O
+x7heuiUSt8l4szi1AcalkzEZI3ExcKrKs0D5VtGZAVQLo6sUuhSF9gQvXkV8eMA=
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/bad_certs/ee-from-missing-intermediate.pem.certspec
@@ -0,0 +1,3 @@
+issuer:Missing Intermediate
+subject:ee-from-missing-intermediate
+extension:subjectAlternativeName:localhost,*.example.com
--- a/security/manager/ssl/tests/unit/bad_certs/moz.build
+++ b/security/manager/ssl/tests/unit/bad_certs/moz.build
@@ -7,42 +7,43 @@
 # Temporarily disabled. See bug 1256495.
 #test_certificates = (
 #    'badSubjectAltNames.pem',
 #    'beforeEpoch.pem',
 #    'beforeEpochINT.pem',
 #    'beforeEpochIssuer.pem',
 #    'ca-used-as-end-entity.pem',
 #    'default-ee.pem',
+#    'ee-from-missing-intermediate.pem',
 #    'eeIssuedByNonCA.pem',
 #    'eeIssuedByV1Cert.pem',
 #    'emptyIssuerName.pem',
 #    'emptyNameCA.pem',
 #    'ev-test-intermediate.pem',
 #    'ev-test.pem',
 #    'evroot.pem',
 #    'expired-ee.pem',
 #    'expiredINT.pem',
 #    'expiredissuer.pem',
 #    'idn-certificate.pem',
 #    'inadequateKeySizeEE.pem',
 #    'inadequatekeyusage-ee.pem',
 #    'ipAddressAsDNSNameInSAN.pem',
 #    'md5signature-expired.pem',
 #    'md5signature.pem',
-#    'mismatchCN.pem',
 #    'mismatch-expired.pem',
 #    'mismatch-notYetValid.pem',
-#    'mismatch.pem',
 #    'mismatch-untrusted-expired.pem',
 #    'mismatch-untrusted.pem',
+#    'mismatch.pem',
+#    'mismatchCN.pem',
+#    'noValidNames.pem',
+#    'notYetValid.pem',
 #    'notYetValidINT.pem',
 #    'notYetValidIssuer.pem',
-#    'notYetValid.pem',
-#    'noValidNames.pem',
 #    'nsCertTypeCritical.pem',
 #    'nsCertTypeCriticalWithExtKeyUsage.pem',
 #    'nsCertTypeNotCritical.pem',
 #    'other-issuer-ee.pem',
 #    'other-test-ca.pem',
 #    'self-signed-EE-with-cA-true.pem',
 #    'selfsigned-inadequateEKU.pem',
 #    'selfsigned.pem',
--- a/security/manager/ssl/tests/unit/moz.build
+++ b/security/manager/ssl/tests/unit/moz.build
@@ -23,16 +23,17 @@ TEST_DIRS += [
     'test_certDB_import',
     'test_content_signing',
     'test_ct',
     'test_ev_certs',
     'test_getchain',
     'test_intermediate_basic_usage_constraints',
     'test_keysize',
     'test_keysize_ev',
+    'test_missing_intermediate',
     'test_name_constraints',
     'test_ocsp_fetch_method',
     'test_ocsp_url',
     'test_onecrl',
     'test_pinning_dynamic',
     'test_startcom_wosign',
     'test_validity',
 ]
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_missing_intermediate.js
@@ -0,0 +1,30 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// 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";
+
+// Tests that if a server does not send a complete certificate chain, we can
+// make use of cached intermediates to build a trust path.
+
+do_get_profile(); // must be called before getting nsIX509CertDB
+const certdb  = Cc["@mozilla.org/security/x509certdb;1"]
+                  .getService(Ci.nsIX509CertDB);
+
+function run_test() {
+  addCertFromFile(certdb, "bad_certs/test-ca.pem", "CTu,,");
+  add_tls_server_setup("BadCertServer", "bad_certs");
+  // If we don't know about the intermediate, we'll get an unknown issuer error.
+  add_connection_test("ee-from-missing-intermediate.example.com",
+                      SEC_ERROR_UNKNOWN_ISSUER);
+  add_test(() => {
+    addCertFromFile(certdb, "test_missing_intermediate/missing-intermediate.pem",
+                    ",,");
+    run_next_test();
+  });
+  // Now that we've cached the intermediate, the connection should succeed.
+  add_connection_test("ee-from-missing-intermediate.example.com",
+                      PRErrorCodeSuccess);
+  run_next_test();
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_missing_intermediate/missing-intermediate.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC3DCCAcagAwIBAgIUX224QtJ2mJrowBNyubHeYknUAZ0wCwYJKoZIhvcNAQEL
+MBIxEDAOBgNVBAMMB1Rlc3QgQ0EwIhgPMjAxNTExMjgwMDAwMDBaGA8yMDE4MDIw
+NTAwMDAwMFowHzEdMBsGA1UEAwwUTWlzc2luZyBJbnRlcm1lZGlhdGUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erk
+NUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwC
+fs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1m
+CyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTM
+HGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m
+1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGj
+HTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMAsGCSqGSIb3DQEBCwOCAQEA
+hOeRxGhMAYiPuIoqU79VoRw99fWG05N6NS8CDoT6gMUBFCDG77rR1g3nR/KzH2df
+eY0K4B6ARLSip8L3zbooFKexzbFo6bEGnJM/OTfojDprE+5nMDwMiIoQMswxzKd0
+9GnSispqPdvWojn5F++1EDv1VQXvjdkxt7iZLUxSJ59ktxGy1bqsnncslH2u/aPN
+gIdVfftPBjRikDilKmMRU1g+g8f3LLCv557D6icvSgMiB8p/i//RCwYqpUfYghcg
+EGU/vzq3746TTbkQvlgKjRS/h+J26atjgsGOVLOeETQurjgHKOj8ngllPrfKm52p
+DqVeWkpMVj4PptI7GVianw==
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_missing_intermediate/missing-intermediate.pem.certspec
@@ -0,0 +1,4 @@
+issuer:Test CA
+subject:Missing Intermediate
+extension:basicConstraints:cA,
+extension:keyUsage:cRLSign,keyCertSign
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_missing_intermediate/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# BadCertServer takes as an argument a path to a directory and loads
+# every key and certificate in it. We want to test what happens when a
+# server doesn't include an intermediate that is necessary to build a
+# complete trust path. The easiest way to do this right now is to put
+# the intermediate in a different directory, so that BadCertServer
+# doesn't know about it and can't send it in the TLS handshake.
+# Temporarily disabled. See bug 1256495.
+#test_certificates = (
+#    'missing-intermediate.pem',
+#)
+#
+#for test_certificate in test_certificates:
+#    GeneratedTestCertificate(test_certificate)
--- a/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp
@@ -72,16 +72,17 @@ const BadCertHost sBadCertHosts[] =
   { "end-entity-issued-by-non-CA.example.com", "eeIssuedByNonCA" },
   { "inadequate-key-size-ee.example.com", "inadequateKeySizeEE" },
   { "badSubjectAltNames.example.com", "badSubjectAltNames" },
   { "ipAddressAsDNSNameInSAN.example.com", "ipAddressAsDNSNameInSAN" },
   { "noValidNames.example.com", "noValidNames" },
   { "bug413909.xn--hxajbheg2az3al.xn--jxalpdlp", "idn-certificate" },
   { "emptyissuername.example.com", "emptyIssuerName" },
   { "ev-test.example.com", "ev-test" },
+  { "ee-from-missing-intermediate.example.com", "ee-from-missing-intermediate" },
   { "localhost", "unknownissuer" },
   { nullptr, nullptr }
 };
 
 int32_t
 DoSNISocketConfigBySubjectCN(PRFileDesc* aFd, const SECItem* aSrvNameArr,
                              uint32_t aSrvNameArrSize)
 {
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -18,16 +18,17 @@ support-files =
   test_certviewer_invalid_oids/**
   test_content_signing/**
   test_ct/**
   test_ev_certs/**
   test_getchain/**
   test_intermediate_basic_usage_constraints/**
   test_keysize/**
   test_keysize_ev/**
+  test_missing_intermediate/**
   test_name_constraints/**
   test_ocsp_fetch_method/**
   test_ocsp_url/**
   test_onecrl/**
   test_pinning_dynamic/**
   test_sdr_preexisting/**
   test_signed_apps/**
   test_signed_dir/**
@@ -89,16 +90,18 @@ skip-if = toolkit == 'android'
 [test_js_cert_override_service.js]
 run-sequentially = hardcoded ports
 [test_keysize.js]
 [test_keysize_ev.js]
 run-sequentially = hardcoded ports
 [test_local_cert.js]
 [test_logoutAndTeardown.js]
 run-sequentially = hardcoded ports
+[test_missing_intermediate.js]
+run-sequentially = hardcoded ports
 [test_name_constraints.js]
 [test_nsCertType.js]
 run-sequentially = hardcoded ports
 [test_nsIX509Cert_utf8.js]
 [test_nsIX509CertValidity.js]
 [test_nss_shutdown.js]
 [test_ocsp_caching.js]
 run-sequentially = hardcoded ports
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -25,17 +25,17 @@
 //!
 //!   o Instead of `get_attr()`, use `.get_attr_val_for_layout()`.
 //!
 //!   o Instead of `html_element_in_html_document()`, use
 //!     `html_element_in_html_document_for_layout()`.
 
 #![allow(unsafe_code)]
 
-use atomic_refcell::AtomicRefCell;
+use atomic_refcell::{AtomicRef, AtomicRefCell};
 use dom::bindings::inheritance::{CharacterDataTypeId, ElementTypeId};
 use dom::bindings::inheritance::{HTMLElementTypeId, NodeTypeId};
 use dom::bindings::js::LayoutJS;
 use dom::bindings::str::extended_filtering;
 use dom::characterdata::LayoutCharacterDataHelpers;
 use dom::document::{Document, LayoutDocumentHelpers, PendingRestyle};
 use dom::element::{Element, LayoutElementHelpers, RawLayoutElementHelpers};
 use dom::node::{CAN_BE_FRAGMENTED, DIRTY_ON_VIEWPORT_SIZE_CHANGE, HAS_DIRTY_DESCENDANTS, IS_IN_DOC};
@@ -1087,18 +1087,20 @@ impl<'le> ThreadSafeLayoutElement for Se
     fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
         self.element.get_attr_enum(namespace, name)
     }
 
     fn get_attr<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> {
         self.element.get_attr(namespace, name)
     }
 
-    fn get_style_data(&self) -> Option<&AtomicRefCell<ElementData>> {
+    fn style_data(&self) -> AtomicRef<ElementData> {
         self.element.get_data()
+            .expect("Unstyled layout node?")
+            .borrow()
     }
 }
 
 /// This implementation of `::selectors::Element` is used for implementing lazy
 /// pseudo-elements.
 ///
 /// Lazy pseudo-elements in Servo only allows selectors using safe properties,
 /// i.e., local_name, attributes, so they can only be used for **private**
--- a/servo/components/script_layout_interface/wrapper_traits.rs
+++ b/servo/components/script_layout_interface/wrapper_traits.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #![allow(unsafe_code)]
 
 use HTMLCanvasData;
 use LayoutNodeType;
 use OpaqueStyleAndLayoutData;
 use SVGSVGData;
-use atomic_refcell::AtomicRefCell;
+use atomic_refcell::AtomicRef;
 use gfx_traits::{ByteIndex, FragmentType, combine_id_with_fragment_type};
 use html5ever::{Namespace, LocalName};
 use msg::constellation_msg::{BrowsingContextId, PipelineId};
 use range::Range;
 use servo_url::ServoUrl;
 use std::fmt::Debug;
 use style::attr::AttrValue;
 use style::computed_values::display;
@@ -333,41 +333,33 @@ pub trait ThreadSafeLayoutElement: Clone
     unsafe fn unsafe_get(self) ->
         <<Self::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteNode as TNode>::ConcreteElement;
 
     #[inline]
     fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str>;
 
     fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue>;
 
-    fn get_style_data(&self) -> Option<&AtomicRefCell<ElementData>>;
+    fn style_data(&self) -> AtomicRef<ElementData>;
 
     #[inline]
     fn get_pseudo_element_type(&self) -> PseudoElementType<Option<display::T>>;
 
     #[inline]
     fn get_before_pseudo(&self) -> Option<Self> {
-        if self.get_style_data()
-               .unwrap()
-               .borrow()
-               .styles().pseudos
-               .has(&PseudoElement::Before) {
+        if self.style_data().styles().pseudos.has(&PseudoElement::Before) {
             Some(self.with_pseudo(PseudoElementType::Before(None)))
         } else {
             None
         }
     }
 
     #[inline]
     fn get_after_pseudo(&self) -> Option<Self> {
-        if self.get_style_data()
-               .unwrap()
-               .borrow()
-               .styles().pseudos
-               .has(&PseudoElement::After) {
+        if self.style_data().styles().pseudos.has(&PseudoElement::After) {
             Some(self.with_pseudo(PseudoElementType::After(None)))
         } else {
             None
         }
     }
 
     #[inline]
     fn get_details_summary_pseudo(&self) -> Option<Self> {
@@ -395,85 +387,76 @@ pub trait ThreadSafeLayoutElement: Clone
     }
 
     /// Returns the style results for the given node. If CSS selector matching
     /// has not yet been performed, fails.
     ///
     /// Unlike the version on TNode, this handles pseudo-elements.
     #[inline]
     fn style(&self, context: &SharedStyleContext) -> Arc<ServoComputedValues> {
+        let data = self.style_data();
         match self.get_pseudo_element_type() {
-            PseudoElementType::Normal => self.get_style_data().unwrap().borrow()
-                                             .styles().primary.values().clone(),
+            PseudoElementType::Normal => {
+                data.styles().primary.values().clone()
+            },
             other => {
                 // Precompute non-eagerly-cascaded pseudo-element styles if not
                 // cached before.
                 let style_pseudo = other.style_pseudo_element();
-                let mut data = self.get_style_data().unwrap().borrow_mut();
                 match style_pseudo.cascade_type() {
                     // Already computed during the cascade.
                     PseudoElementCascadeType::Eager => {
-                        data.styles().pseudos.get(&style_pseudo)
+                        self.style_data()
+                            .styles().pseudos.get(&style_pseudo)
                             .unwrap().values().clone()
                     },
                     PseudoElementCascadeType::Precomputed => {
-                        if !data.styles().cached_pseudos.contains_key(&style_pseudo) {
-                            let new_style =
-                                context.stylist.precomputed_values_for_pseudo(
-                                    &context.guards,
-                                    &style_pseudo,
-                                    Some(data.styles().primary.values()),
-                                    CascadeFlags::empty(),
-                                    &ServoMetricsProvider);
-                            data.styles_mut().cached_pseudos
-                                .insert(style_pseudo.clone(), new_style);
-                        }
-                        data.styles().cached_pseudos.get(&style_pseudo)
-                            .unwrap().values().clone()
+                        context.stylist.precomputed_values_for_pseudo(
+                            &context.guards,
+                            &style_pseudo,
+                            Some(data.styles().primary.values()),
+                            CascadeFlags::empty(),
+                            &ServoMetricsProvider)
+                            .values().clone()
                     }
                     PseudoElementCascadeType::Lazy => {
-                        if !data.styles().cached_pseudos.contains_key(&style_pseudo) {
-                            let new_style =
-                                context.stylist
-                                       .lazily_compute_pseudo_element_style(
-                                           &context.guards,
-                                           unsafe { &self.unsafe_get() },
-                                           &style_pseudo,
-                                           data.styles().primary.values(),
-                                           &ServoMetricsProvider);
-                            data.styles_mut().cached_pseudos
-                                .insert(style_pseudo.clone(), new_style.unwrap());
-                        }
-                        data.styles().cached_pseudos.get(&style_pseudo)
-                            .unwrap().values().clone()
+                        context.stylist
+                               .lazily_compute_pseudo_element_style(
+                                   &context.guards,
+                                   unsafe { &self.unsafe_get() },
+                                   &style_pseudo,
+                                   data.styles().primary.values(),
+                                   &ServoMetricsProvider)
+                               .unwrap()
+                               .values().clone()
                     }
                 }
             }
         }
     }
 
     #[inline]
     fn selected_style(&self) -> Arc<ServoComputedValues> {
-        let data = self.get_style_data().unwrap().borrow();
+        let data = self.style_data();
         data.styles().pseudos
             .get(&PseudoElement::Selection).map(|s| s)
             .unwrap_or(&data.styles().primary)
             .values().clone()
     }
 
     /// Returns the already resolved style of the node.
     ///
     /// This differs from `style(ctx)` in that if the pseudo-element has not yet
     /// been computed it would panic.
     ///
     /// This should be used just for querying layout, or when we know the
     /// element style is precomputed, not from general layout itself.
     #[inline]
     fn resolved_style(&self) -> Arc<ServoComputedValues> {
-        let data = self.get_style_data().unwrap().borrow();
+        let data = self.style_data();
         match self.get_pseudo_element_type() {
             PseudoElementType::Normal
                 => data.styles().primary.values().clone(),
             other
                 => data.styles().pseudos
                        .get(&other.style_pseudo_element()).unwrap().values().clone(),
         }
     }
--- a/servo/components/style/data.rs
+++ b/servo/components/style/data.rs
@@ -7,19 +7,17 @@
 use context::SharedStyleContext;
 use dom::TElement;
 use properties::ComputedValues;
 use properties::longhands::display::computed_value as display;
 use restyle_hints::{HintComputationContext, RestyleReplacements, RestyleHint};
 use rule_tree::StrongRuleNode;
 use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
 use shared_lock::StylesheetGuards;
-#[cfg(feature = "servo")] use std::collections::HashMap;
 use std::fmt;
-#[cfg(feature = "servo")] use std::hash::BuildHasherDefault;
 use stylearc::Arc;
 use traversal::TraversalFlags;
 
 /// The structure that represents the result of style computation. This is
 /// effectively a tuple of rules and computed values, that is, the rule node,
 /// and the result of computing that rule node's rules, the `ComputedValues`.
 #[derive(Clone)]
 pub struct ComputedStyle {
@@ -140,43 +138,32 @@ impl EagerPseudoStyles {
         debug_assert!(self.has(pseudo));
         let mut style = self.get_mut(pseudo).unwrap();
         let changed = style.rules != rules;
         style.rules = rules;
         changed
     }
 }
 
-/// A cache of precomputed and lazy pseudo-elements, used by servo. This isn't
-/// a very efficient design, but is the result of servo having previously used
-/// the eager pseudo map (when it was a map) for this cache.
-#[cfg(feature = "servo")]
-type PseudoElementCache = HashMap<PseudoElement, ComputedStyle, BuildHasherDefault<::fnv::FnvHasher>>;
-#[cfg(feature = "gecko")]
-type PseudoElementCache = ();
-
 /// The styles associated with a node, including the styles for any
 /// pseudo-elements.
 #[derive(Clone, Debug)]
 pub struct ElementStyles {
     /// The element's style.
     pub primary: ComputedStyle,
     /// A list of the styles for the element's eagerly-cascaded pseudo-elements.
     pub pseudos: EagerPseudoStyles,
-    /// NB: This is an empty field for gecko.
-    pub cached_pseudos: PseudoElementCache,
 }
 
 impl ElementStyles {
     /// Trivially construct a new `ElementStyles`.
     pub fn new(primary: ComputedStyle) -> Self {
         ElementStyles {
             primary: primary,
             pseudos: EagerPseudoStyles(None),
-            cached_pseudos: PseudoElementCache::default(),
         }
     }
 
     /// Whether this element `display` value is `none`.
     pub fn is_display_none(&self) -> bool {
         self.primary.values().get_box().clone_display() == display::T::none
     }
 }
--- a/servo/components/style/gecko/data.rs
+++ b/servo/components/style/gecko/data.rs
@@ -1,86 +1,63 @@
 /* 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/. */
 
 //! Data needed to style a Gecko document.
 
 use Atom;
-use animation::Animation;
 use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
-use dom::OpaqueNode;
+use fnv::FnvHashMap;
 use gecko::rules::{CounterStyleRule, FontFaceRule};
 use gecko_bindings::bindings::RawServoStyleSet;
 use gecko_bindings::structs::RawGeckoPresContextOwned;
 use gecko_bindings::structs::nsIDocument;
 use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
 use media_queries::Device;
-use parking_lot::RwLock;
 use properties::ComputedValues;
 use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard};
-use std::collections::HashMap;
-use std::sync::mpsc::{Receiver, Sender, channel};
 use stylearc::Arc;
 use stylesheet_set::StylesheetSet;
 use stylesheets::Origin;
 use stylist::{ExtraStyleData, Stylist};
 
 /// The container for data that a Servo-backed Gecko document needs to style
 /// itself.
 pub struct PerDocumentStyleDataImpl {
     /// Rule processor.
     pub stylist: Stylist,
 
     /// List of stylesheets, mirrored from Gecko.
     pub stylesheets: StylesheetSet,
 
-    // FIXME(bholley): Hook these up to something.
-    /// Unused. Will go away when we actually implement transitions and
-    /// animations properly.
-    pub new_animations_sender: Sender<Animation>,
-    /// Unused. Will go away when we actually implement transitions and
-    /// animations properly.
-    pub new_animations_receiver: Receiver<Animation>,
-    /// Unused. Will go away when we actually implement transitions and
-    /// animations properly.
-    pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
-    /// Unused. Will go away when we actually implement transitions and
-    /// animations properly.
-    pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
-
     /// List of effective font face rules.
     pub font_faces: Vec<(Arc<Locked<FontFaceRule>>, Origin)>,
+
     /// Map for effective counter style rules.
-    pub counter_styles: HashMap<Atom, Arc<Locked<CounterStyleRule>>>,
+    pub counter_styles: FnvHashMap<Atom, Arc<Locked<CounterStyleRule>>>,
 }
 
 /// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
 /// and unexpected races while trying to mutate it.
 pub struct PerDocumentStyleData(AtomicRefCell<PerDocumentStyleDataImpl>);
 
 impl PerDocumentStyleData {
     /// Create a dummy `PerDocumentStyleData`.
     pub fn new(pres_context: RawGeckoPresContextOwned) -> Self {
         let device = Device::new(pres_context);
         let quirks_mode = unsafe {
             (*(*device.pres_context).mDocument.raw::<nsIDocument>()).mCompatMode
         };
 
-        let (new_anims_sender, new_anims_receiver) = channel();
-
         PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
             stylist: Stylist::new(device, quirks_mode.into()),
             stylesheets: StylesheetSet::new(),
-            new_animations_sender: new_anims_sender,
-            new_animations_receiver: new_anims_receiver,
-            running_animations: Arc::new(RwLock::new(HashMap::new())),
-            expired_animations: Arc::new(RwLock::new(HashMap::new())),
             font_faces: vec![],
-            counter_styles: HashMap::new(),
+            counter_styles: FnvHashMap::default(),
         }))
     }
 
     /// Get an immutable reference to this style data.
     pub fn borrow(&self) -> AtomicRef<PerDocumentStyleDataImpl> {
         self.0.borrow()
     }
 
--- a/servo/components/style/gecko/pseudo_element.rs
+++ b/servo/components/style/gecko/pseudo_element.rs
@@ -44,16 +44,25 @@ impl PseudoElement {
 
         if self.is_anon_box() {
             return PseudoElementCascadeType::Precomputed
         }
 
         PseudoElementCascadeType::Lazy
     }
 
+    /// Whether the pseudo-element should inherit from the default computed
+    /// values instead of from the parent element.
+    ///
+    /// This is not the common thing, but there are some pseudos (namely:
+    /// ::backdrop), that shouldn't inherit from the parent element.
+    pub fn inherits_from_default_values(&self) -> bool {
+        matches!(*self, PseudoElement::Backdrop)
+    }
+
     /// Gets the canonical index of this eagerly-cascaded pseudo-element.
     #[inline]
     pub fn eager_index(&self) -> usize {
         EAGER_PSEUDOS.iter().position(|p| p == self)
             .expect("Not an eager pseudo")
     }
 
     /// Creates a pseudo-element from an eager index.
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -115,16 +115,17 @@ pub mod keyframes;
 pub mod logical_geometry;
 pub mod matching;
 pub mod media_queries;
 pub mod parallel;
 pub mod parser;
 pub mod restyle_hints;
 pub mod rule_tree;
 pub mod scoped_tls;
+pub mod selector_map;
 pub mod selector_parser;
 pub mod shared_lock;
 pub mod sharing;
 pub mod stylist;
 #[cfg(feature = "servo")] #[allow(unsafe_code)] pub mod servo;
 pub mod sequential;
 pub mod sink;
 pub mod str;
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -2,17 +2,16 @@
  * 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/. */
 
 //! High-level interface to CSS selector matching.
 
 #![allow(unsafe_code)]
 #![deny(missing_docs)]
 
-use atomic_refcell::AtomicRefMut;
 use cascade_info::CascadeInfo;
 use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
 use data::{ComputedStyle, ElementData, RestyleData};
 use dom::{AnimationRules, TElement, TNode};
 use font_metrics::FontMetricsProvider;
 use log::LogLevel::Trace;
 use properties::{CascadeFlags, ComputedValues, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
 use properties::longhands::display::computed_value as display;
@@ -964,17 +963,17 @@ pub trait MatchMethods : TElement {
     }
 
     /// Updates the rule nodes without re-running selector matching, using just
     /// the rule tree. Returns RulesChanged which indicates whether the rule nodes changed
     /// and whether the important rules changed.
     fn replace_rules(&self,
                      replacements: RestyleReplacements,
                      context: &StyleContext<Self>,
-                     data: &mut AtomicRefMut<ElementData>)
+                     data: &mut ElementData)
                      -> RulesChanged {
         use properties::PropertyDeclarationBlock;
         use shared_lock::Locked;
 
         let element_styles = &mut data.styles_mut();
         let primary_rules = &mut element_styles.primary.rules;
         let mut result = RulesChanged::empty();
 
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -11,29 +11,28 @@ use LocalName;
 use Namespace;
 use context::{SharedStyleContext, ThreadLocalStyleContext};
 use dom::TElement;
 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::matches_selector;
 use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorMethods};
 use selectors::visitor::SelectorVisitor;
 use smallvec::SmallVec;
-use std::borrow::Borrow;
 use std::cell::Cell;
 use std::clone::Clone;
 use std::cmp;
-use stylist::SelectorMap;
 
 /// When the ElementState of an element (like IN_HOVER_STATE) changes,
 /// certain pseudo-classes (like :hover) may require us to restyle that
 /// element, its siblings, and/or its descendants. Similarly, when various
 /// attributes of an element change, we may also need to restyle things with
 /// id, class, and attribute selectors. Doing this conservatively is
 /// expensive, and so we use RestyleHints to short-circuit work we know is
 /// unnecessary.
@@ -754,54 +753,22 @@ pub struct Dependency {
     #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
     selector: SelectorInner<SelectorImpl>,
     /// The hint associated with this dependency.
     pub hint: RestyleHint,
     /// The sensitivities associated with this dependency.
     pub sensitivities: Sensitivities,
 }
 
-impl Borrow<SelectorInner<SelectorImpl>> for Dependency {
-    fn borrow(&self) -> &SelectorInner<SelectorImpl> {
+impl SelectorMapEntry for Dependency {
+    fn selector(&self) -> &SelectorInner<SelectorImpl> {
         &self.selector
     }
 }
 
-/// A similar version of the above, but for pseudo-elements, which only care
-/// about the full selector, and need it in order to properly track
-/// pseudo-element selector state.
-///
-/// NOTE(emilio): We could add a `hint` and `sensitivities` field to the
-/// `PseudoElementDependency` and stop posting `RESTYLE_DESCENDANTS`s hints if
-/// we visited all the pseudo-elements of an element unconditionally as part of
-/// the traversal.
-///
-/// That would allow us to stop posting `RESTYLE_DESCENDANTS` hints for dumb
-/// selectors, and storing pseudo dependencies in the element dependency map.
-///
-/// That would allow us to avoid restyling the element itself when a selector
-/// has only changed a pseudo-element's style, too.
-///
-/// There's no good way to do that right now though, and I think for the
-/// foreseeable future we may just want to optimize that `RESTYLE_DESCENDANTS`
-/// to become a `RESTYLE_PSEUDO_ELEMENTS` or something like that, in order to at
-/// least not restyle the whole subtree.
-#[derive(Clone, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-struct PseudoElementDependency {
-    #[cfg_attr(feature = "servo", ignore_heap_size_of = "defined in selectors")]
-    selector: Selector<SelectorImpl>,
-}
-
-impl Borrow<SelectorInner<SelectorImpl>> for PseudoElementDependency {
-    fn borrow(&self) -> &SelectorInner<SelectorImpl> {
-        &self.selector.inner
-    }
-}
-
 /// The following visitor visits all the simple selectors for a given complex
 /// selector, taking care of :not and :any combinators, collecting whether any
 /// of them is sensitive to attribute or state changes.
 struct SensitivitiesVisitor {
     sensitivities: Sensitivities,
 }
 
 impl SelectorVisitor for SensitivitiesVisitor {
new file mode 100644
--- /dev/null
+++ b/servo/components/style/selector_map.rs
@@ -0,0 +1,455 @@
+/* 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/. */
+
+//! A data structure to efficiently index structs containing selectors by local
+//! name, ids and hash.
+
+use {Atom, LocalName};
+use dom::TElement;
+use fnv::FnvHashMap;
+use pdqsort::sort_by;
+use rule_tree::CascadeLevel;
+use selector_parser::SelectorImpl;
+use selectors::matching::{matches_selector, MatchingContext, ElementSelectorFlags};
+use selectors::parser::{Component, Combinator, SelectorInner};
+use selectors::parser::LocalName as LocalNameSelector;
+use smallvec::VecLike;
+use std::borrow::Borrow;
+use std::collections::HashMap;
+use std::hash::Hash;
+use stylist::{ApplicableDeclarationBlock, Rule};
+
+/// A trait to abstract over a given selector map entry.
+pub trait SelectorMapEntry : Sized + Clone {
+    /// Get the selector we should use to index in the selector map.
+    fn selector(&self) -> &SelectorInner<SelectorImpl>;
+}
+
+impl SelectorMapEntry for SelectorInner<SelectorImpl> {
+    fn selector(&self) -> &SelectorInner<SelectorImpl> {
+        self
+    }
+}
+
+/// Map element data to selector-providing objects for which the last simple
+/// selector starts with them.
+///
+/// e.g.,
+/// "p > img" would go into the set of selectors corresponding to the
+/// element "img"
+/// "a .foo .bar.baz" would go into the set of selectors corresponding to
+/// the class "bar"
+///
+/// Because we match selectors right-to-left (i.e., moving up the tree
+/// from an element), we need to compare the last simple selector in the
+/// selector with the element.
+///
+/// So, if an element has ID "id1" and classes "foo" and "bar", then all
+/// the rules it matches will have their last simple selector starting
+/// either with "#id1" or with ".foo" or with ".bar".
+///
+/// Hence, the union of the rules keyed on each of element's classes, ID,
+/// element name, etc. will contain the Selectors that actually match that
+/// element.
+///
+/// TODO: Tune the initial capacity of the HashMap
+#[derive(Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct SelectorMap<T: SelectorMapEntry> {
+    /// A hash from an ID to rules which contain that ID selector.
+    pub id_hash: FnvHashMap<Atom, Vec<T>>,
+    /// A hash from a class name to rules which contain that class selector.
+    pub class_hash: FnvHashMap<Atom, Vec<T>>,
+    /// A hash from local name to rules which contain that local name selector.
+    pub local_name_hash: FnvHashMap<LocalName, Vec<T>>,
+    /// Rules that don't have ID, class, or element selectors.
+    pub other: Vec<T>,
+    /// The number of entries in this map.
+    pub count: usize,
+}
+
+#[inline]
+fn sort_by_key<T, F: Fn(&T) -> K, K: Ord>(v: &mut [T], f: F) {
+    sort_by(v, |a, b| f(a).cmp(&f(b)))
+}
+
+impl<T: SelectorMapEntry> SelectorMap<T> {
+    /// Trivially constructs an empty `SelectorMap`.
+    pub fn new() -> Self {
+        SelectorMap {
+            id_hash: HashMap::default(),
+            class_hash: HashMap::default(),
+            local_name_hash: HashMap::default(),
+            other: Vec::new(),
+            count: 0,
+        }
+    }
+
+    /// Returns whether there are any entries in the map.
+    pub fn is_empty(&self) -> bool {
+        self.count == 0
+    }
+
+    /// Returns the number of entries.
+    pub fn len(&self) -> usize {
+        self.count
+    }
+}
+
+impl SelectorMap<Rule> {
+    /// Append to `rule_list` all Rules in `self` that match element.
+    ///
+    /// Extract matching rules as per element's ID, classes, tag name, etc..
+    /// Sort the Rules at the end to maintain cascading order.
+    pub fn get_all_matching_rules<E, V, F>(&self,
+                                           element: &E,
+                                           rule_hash_target: &E,
+                                           matching_rules_list: &mut V,
+                                           context: &mut MatchingContext,
+                                           flags_setter: &mut F,
+                                           cascade_level: CascadeLevel)
+        where E: TElement,
+              V: VecLike<ApplicableDeclarationBlock>,
+              F: FnMut(&E, ElementSelectorFlags),
+    {
+        if self.is_empty() {
+            return
+        }
+
+        // At the end, we're going to sort the rules that we added, so remember where we began.
+        let init_len = matching_rules_list.len();
+        if let Some(id) = rule_hash_target.get_id() {
+            SelectorMap::get_matching_rules_from_hash(element,
+                                                      &self.id_hash,
+                                                      &id,
+                                                      matching_rules_list,
+                                                      context,
+                                                      flags_setter,
+                                                      cascade_level)
+        }
+
+        rule_hash_target.each_class(|class| {
+            SelectorMap::get_matching_rules_from_hash(element,
+                                                      &self.class_hash,
+                                                      class,
+                                                      matching_rules_list,
+                                                      context,
+                                                      flags_setter,
+                                                      cascade_level);
+        });
+
+        SelectorMap::get_matching_rules_from_hash(element,
+                                                  &self.local_name_hash,
+                                                  rule_hash_target.get_local_name(),
+                                                  matching_rules_list,
+                                                  context,
+                                                  flags_setter,
+                                                  cascade_level);
+
+        SelectorMap::get_matching_rules(element,
+                                        &self.other,
+                                        matching_rules_list,
+                                        context,
+                                        flags_setter,
+                                        cascade_level);
+
+        // Sort only the rules we just added.
+        sort_by_key(&mut matching_rules_list[init_len..],
+                    |block| (block.specificity, block.source_order));
+    }
+
+    /// Append to `rule_list` all universal Rules (rules with selector `*|*`) in
+    /// `self` sorted by specificity and source order.
+    pub fn get_universal_rules(&self,
+                               cascade_level: CascadeLevel)
+                               -> Vec<ApplicableDeclarationBlock> {
+        debug_assert!(!cascade_level.is_important());
+        if self.is_empty() {
+            return vec![];
+        }
+
+        let mut rules_list = vec![];
+        for rule in self.other.iter() {
+            if rule.selector.is_universal() {
+                rules_list.push(rule.to_applicable_declaration_block(cascade_level))
+            }
+        }
+
+        sort_by_key(&mut rules_list,
+                    |block| (block.specificity, block.source_order));
+
+        rules_list
+    }
+
+    fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector, F>(
+        element: &E,
+        hash: &FnvHashMap<Str, Vec<Rule>>,
+        key: &BorrowedStr,
+        matching_rules: &mut Vector,
+        context: &mut MatchingContext,
+        flags_setter: &mut F,
+        cascade_level: CascadeLevel)
+        where E: TElement,
+              Str: Borrow<BorrowedStr> + Eq + Hash,
+              BorrowedStr: Eq + Hash,
+              Vector: VecLike<ApplicableDeclarationBlock>,
+              F: FnMut(&E, ElementSelectorFlags),
+    {
+        if let Some(rules) = hash.get(key) {
+            SelectorMap::get_matching_rules(element,
+                                            rules,
+                                            matching_rules,
+                                            context,
+                                            flags_setter,
+                                            cascade_level)
+        }
+    }
+
+    /// Adds rules in `rules` that match `element` to the `matching_rules` list.
+    fn get_matching_rules<E, V, F>(element: &E,
+                                   rules: &[Rule],
+                                   matching_rules: &mut V,
+                                   context: &mut MatchingContext,
+                                   flags_setter: &mut F,
+                                   cascade_level: CascadeLevel)
+        where E: TElement,
+              V: VecLike<ApplicableDeclarationBlock>,
+              F: FnMut(&E, ElementSelectorFlags),
+    {
+        for rule in rules {
+            if matches_selector(&rule.selector.inner,
+                                element,
+                                context,
+                                flags_setter) {
+                matching_rules.push(
+                    rule.to_applicable_declaration_block(cascade_level));
+            }
+        }
+    }
+}
+
+impl<T: SelectorMapEntry> SelectorMap<T> {
+    /// Inserts into the correct hash, trying id, class, and localname.
+    pub fn insert(&mut self, entry: T) {
+        self.count += 1;
+
+        if let Some(id_name) = get_id_name(entry.selector()) {
+            find_push(&mut self.id_hash, id_name, entry);
+            return;
+        }
+
+        if let Some(class_name) = get_class_name(entry.selector()) {
+            find_push(&mut self.class_hash, class_name, entry);
+            return;
+        }
+
+        if let Some(LocalNameSelector { name, lower_name }) = get_local_name(entry.selector()) {
+            // If the local name in the selector isn't lowercase, insert it into
+            // the rule hash twice. This means that, during lookup, we can always
+            // find the rules based on the local name of the element, regardless
+            // of whether it's an html element in an html document (in which case
+            // we match against lower_name) or not (in which case we match against
+            // name).
+            //
+            // In the case of a non-html-element-in-html-document with a
+            // lowercase localname and a non-lowercase selector, the rulehash
+            // lookup may produce superfluous selectors, but the subsequent
+            // selector matching work will filter them out.
+            if name != lower_name {
+                find_push(&mut self.local_name_hash, lower_name, entry.clone());
+            }
+            find_push(&mut self.local_name_hash, name, entry);
+
+            return;
+        }
+
+        self.other.push(entry);
+    }
+
+    /// Looks up entries by id, class, local name, and other (in order).
+    ///
+    /// Each entry is passed to the callback, which returns true to continue
+    /// iterating entries, or false to terminate the lookup.
+    ///
+    /// Returns false if the callback ever returns false.
+    ///
+    /// FIXME(bholley) This overlaps with SelectorMap<Rule>::get_all_matching_rules,
+    /// but that function is extremely hot and I'd rather not rearrange it.
+    #[inline]
+    pub fn lookup<E, F>(&self, element: E, f: &mut F) -> bool
+        where E: TElement,
+              F: FnMut(&T) -> bool
+    {
+        // Id.
+        if let Some(id) = element.get_id() {
+            if let Some(v) = self.id_hash.get(&id) {
+                for entry in v.iter() {
+                    if !f(&entry) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        // Class.
+        let mut done = false;
+        element.each_class(|class| {
+            if !done {
+                if let Some(v) = self.class_hash.get(class) {
+                    for entry in v.iter() {
+                        if !f(&entry) {
+                            done = true;
+                            return;
+                        }
+                    }
+                }
+            }
+        });
+        if done {
+            return false;
+        }
+
+        // Local name.
+        if let Some(v) = self.local_name_hash.get(element.get_local_name()) {
+            for entry in v.iter() {
+                if !f(&entry) {
+                    return false;
+                }
+            }
+        }
+
+        // Other.
+        for entry in self.other.iter() {
+            if !f(&entry) {
+                return false;
+            }
+        }
+
+        true
+    }
+
+    /// Performs a normal lookup, and also looks up entries for the passed-in
+    /// id and classes.
+    ///
+    /// Each entry is passed to the callback, which returns true to continue
+    /// iterating entries, or false to terminate the lookup.
+    ///
+    /// Returns false if the callback ever returns false.
+    #[inline]
+    pub fn lookup_with_additional<E, F>(&self,
+                                        element: E,
+                                        additional_id: Option<Atom>,
+                                        additional_classes: &[Atom],
+                                        f: &mut F)
+                                        -> bool
+        where E: TElement,
+              F: FnMut(&T) -> bool
+    {
+        // Do the normal lookup.
+        if !self.lookup(element, f) {
+            return false;
+        }
+
+        // Check the additional id.
+        if let Some(id) = additional_id {
+            if let Some(v) = self.id_hash.get(&id) {
+                for entry in v.iter() {
+                    if !f(&entry) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        // Check the additional classes.
+        for class in additional_classes {
+            if let Some(v) = self.class_hash.get(class) {
+                for entry in v.iter() {
+                    if !f(&entry) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        true
+    }
+}
+
+/// Searches the selector from right to left, beginning to the left of the
+/// ::pseudo-element (if any), and ending at the first combinator.
+///
+/// The first non-None value returned from |f| is returned.
+///
+/// Effectively, pseudo-elements are ignored, given only state pseudo-classes
+/// may appear before them.
+fn find_from_right<F, R>(selector: &SelectorInner<SelectorImpl>,
+                         mut f: F)
+                         -> Option<R>
+    where F: FnMut(&Component<SelectorImpl>) -> Option<R>,
+{
+    let mut iter = selector.complex.iter();
+    for ss in &mut iter {
+        if let Some(r) = f(ss) {
+            return Some(r)
+        }
+    }
+
+    if iter.next_sequence() == Some(Combinator::PseudoElement) {
+        for ss in &mut iter {
+            if let Some(r) = f(ss) {
+                return Some(r)
+            }
+        }
+    }
+
+    None
+}
+
+/// Retrieve the first ID name in the selector, or None otherwise.
+pub fn get_id_name(selector: &SelectorInner<SelectorImpl>)
+               -> Option<Atom> {
+    find_from_right(selector, |ss| {
+        // TODO(pradeep): Implement case-sensitivity based on the
+        // document type and quirks mode.
+        if let Component::ID(ref id) = *ss {
+            return Some(id.clone());
+        }
+        None
+    })
+}
+
+/// Retrieve the FIRST class name in the selector, or None otherwise.
+pub fn get_class_name(selector: &SelectorInner<SelectorImpl>)
+                  -> Option<Atom> {
+    find_from_right(selector, |ss| {
+        // TODO(pradeep): Implement case-sensitivity based on the
+        // document type and quirks mode.
+        if let Component::Class(ref class) = *ss {
+            return Some(class.clone());
+        }
+        None
+    })
+}
+
+/// Retrieve the name if it is a type selector, or None otherwise.
+pub fn get_local_name(selector: &SelectorInner<SelectorImpl>)
+                  -> Option<LocalNameSelector<SelectorImpl>> {
+    find_from_right(selector, |ss| {
+        if let Component::LocalName(ref n) = *ss {
+            return Some(LocalNameSelector {
+                name: n.name.clone(),
+                lower_name: n.lower_name.clone(),
+            })
+        }
+        None
+    })
+}
+
+#[inline]
+fn find_push<Str: Eq + Hash, V>(map: &mut FnvHashMap<Str, Vec<V>>,
+                                key: Str,
+                                value: V) {
+    map.entry(key).or_insert_with(Vec::new).push(value)
+}
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -11,37 +11,33 @@ use data::ComputedStyle;
 use dom::{AnimationRules, TElement};
 use element_state::ElementState;
 use error_reporting::RustLogReporter;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::nsIAtom;
 use keyframes::KeyframesAnimation;
 use media_queries::Device;
-use pdqsort::sort_by;
 use properties::{self, CascadeFlags, ComputedValues};
 #[cfg(feature = "servo")]
 use properties::INHERIT_ALL;
 use properties::PropertyDeclarationBlock;
 use restyle_hints::{HintComputationContext, DependencySet, RestyleHint};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
+use selector_map::{SelectorMap, SelectorMapEntry};
 use selector_parser::{SelectorImpl, PseudoElement};
 use selectors::attr::NamespaceConstraint;
 use selectors::bloom::BloomFilter;
 use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
 use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode};
-use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorIter};
-use selectors::parser::{SelectorMethods, LocalName as LocalNameSelector};
+use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorIter, SelectorMethods};
 use selectors::visitor::SelectorVisitor;
 use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
 use sink::Push;
 use smallvec::{SmallVec, VecLike};
-use std::borrow::Borrow;
-use std::collections::HashMap;
-use std::hash::Hash;
 #[cfg(feature = "servo")]
 use std::marker::PhantomData;
 use style_traits::viewport::ViewportConstraints;
 use stylearc::Arc;
 #[cfg(feature = "gecko")]
 use stylesheets::{CounterStyleRule, FontFaceRule};
 use stylesheets::{CssRule, Origin};
 use stylesheets::{StyleRule, Stylesheet, UserAgentStylesheets};
@@ -169,17 +165,17 @@ pub struct Stylist {
 
 /// This struct holds data which user of Stylist may want to extract
 /// from stylesheets which can be done at the same time as updating.
 #[cfg(feature = "gecko")]
 pub struct ExtraStyleData<'a> {
     /// A list of effective font-face rules and their origin.
     pub font_faces: &'a mut Vec<(Arc<Locked<FontFaceRule>>, Origin)>,
     /// A map of effective counter-style rules.
-    pub counter_styles: &'a mut HashMap<Atom, Arc<Locked<CounterStyleRule>>>,
+    pub counter_styles: &'a mut FnvHashMap<Atom, Arc<Locked<CounterStyleRule>>>,
 }
 
 #[cfg(feature = "gecko")]
 impl<'a> ExtraStyleData<'a> {
     /// Clear the internal data.
     fn clear(&mut self) {
         self.font_faces.clear();
         self.counter_styles.clear();
@@ -623,17 +619,17 @@ impl Stylist {
     /// :selection.
     ///
     /// Check the documentation on lazy pseudo-elements in
     /// docs/components/style.md
     pub fn lazily_compute_pseudo_element_style<E>(&self,
                                                   guards: &StylesheetGuards,
                                                   element: &E,
                                                   pseudo: &PseudoElement,
-                                                  parent: &Arc<ComputedValues>,
+                                                  parent_style: &ComputedValues,
                                                   font_metrics: &FontMetricsProvider)
                                                   -> Option<ComputedStyle>
         where E: TElement,
     {
         let rule_node =
             match self.lazy_pseudo_rules(guards, element, pseudo) {
                 Some(rule_node) => rule_node,
                 None => return None
@@ -642,18 +638,18 @@ impl Stylist {
         // Read the comment on `precomputed_values_for_pseudo` to see why it's
         // difficult to assert that display: contents nodes never arrive here
         // (tl;dr: It doesn't apply for replaced elements and such, but the
         // computed value is still "contents").
         let computed =
             properties::cascade(&self.device,
                                 &rule_node,
                                 guards,
-                                Some(&**parent),
-                                Some(&**parent),
+                                Some(parent_style),
+                                Some(parent_style),
                                 None,
                                 &RustLogReporter,
                                 font_metrics,
                                 CascadeFlags::empty(),
                                 self.quirks_mode);
 
         Some(ComputedStyle::new(rule_node, Arc::new(computed)))
     }
@@ -1232,427 +1228,16 @@ impl PerPseudoElementSelectorMap {
         match *origin {
             Origin::UserAgent => &mut self.user_agent,
             Origin::Author => &mut self.author,
             Origin::User => &mut self.user,
         }
     }
 }
 
-/// Map element data to selector-providing objects for which the last simple
-/// selector starts with them.
-///
-/// e.g.,
-/// "p > img" would go into the set of selectors corresponding to the
-/// element "img"
-/// "a .foo .bar.baz" would go into the set of selectors corresponding to
-/// the class "bar"
-///
-/// Because we match selectors right-to-left (i.e., moving up the tree
-/// from an element), we need to compare the last simple selector in the
-/// selector with the element.
-///
-/// So, if an element has ID "id1" and classes "foo" and "bar", then all
-/// the rules it matches will have their last simple selector starting
-/// either with "#id1" or with ".foo" or with ".bar".
-///
-/// Hence, the union of the rules keyed on each of element's classes, ID,
-/// element name, etc. will contain the Selectors that actually match that
-/// element.
-///
-/// TODO: Tune the initial capacity of the HashMap
-#[derive(Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct SelectorMap<T: Clone + Borrow<SelectorInner<SelectorImpl>>> {
-    /// A hash from an ID to rules which contain that ID selector.
-    pub id_hash: FnvHashMap<Atom, Vec<T>>,
-    /// A hash from a class name to rules which contain that class selector.
-    pub class_hash: FnvHashMap<Atom, Vec<T>>,
-    /// A hash from local name to rules which contain that local name selector.
-    pub local_name_hash: FnvHashMap<LocalName, Vec<T>>,
-    /// Rules that don't have ID, class, or element selectors.
-    pub other: Vec<T>,
-    /// The number of entries in this map.
-    pub count: usize,
-}
-
-#[inline]
-fn sort_by_key<T, F: Fn(&T) -> K, K: Ord>(v: &mut [T], f: F) {
-    sort_by(v, |a, b| f(a).cmp(&f(b)))
-}
-
-impl<T> SelectorMap<T> where T: Clone + Borrow<SelectorInner<SelectorImpl>> {
-    /// Trivially constructs an empty `SelectorMap`.
-    pub fn new() -> Self {
-        SelectorMap {
-            id_hash: HashMap::default(),
-            class_hash: HashMap::default(),
-            local_name_hash: HashMap::default(),
-            other: Vec::new(),
-            count: 0,
-        }
-    }
-
-    /// Returns whether there are any entries in the map.
-    pub fn is_empty(&self) -> bool {
-        self.count == 0
-    }
-
-    /// Returns the number of entries.
-    pub fn len(&self) -> usize {
-        self.count
-    }
-}
-
-impl SelectorMap<Rule> {
-    /// Append to `rule_list` all Rules in `self` that match element.
-    ///
-    /// Extract matching rules as per element's ID, classes, tag name, etc..
-    /// Sort the Rules at the end to maintain cascading order.
-    pub fn get_all_matching_rules<E, V, F>(&self,
-                                           element: &E,
-                                           rule_hash_target: &E,
-                                           matching_rules_list: &mut V,
-                                           context: &mut MatchingContext,
-                                           flags_setter: &mut F,
-                                           cascade_level: CascadeLevel)
-        where E: TElement,
-              V: VecLike<ApplicableDeclarationBlock>,
-              F: FnMut(&E, ElementSelectorFlags),
-    {
-        if self.is_empty() {
-            return
-        }
-
-        // At the end, we're going to sort the rules that we added, so remember where we began.
-        let init_len = matching_rules_list.len();
-        if let Some(id) = rule_hash_target.get_id() {
-            SelectorMap::get_matching_rules_from_hash(element,
-                                                      &self.id_hash,
-                                                      &id,
-                                                      matching_rules_list,
-                                                      context,
-                                                      flags_setter,
-                                                      cascade_level)
-        }
-
-        rule_hash_target.each_class(|class| {
-            SelectorMap::get_matching_rules_from_hash(element,
-                                                      &self.class_hash,
-                                                      class,
-                                                      matching_rules_list,
-                                                      context,
-                                                      flags_setter,
-                                                      cascade_level);
-        });
-
-        SelectorMap::get_matching_rules_from_hash(element,
-                                                  &self.local_name_hash,
-                                                  rule_hash_target.get_local_name(),
-                                                  matching_rules_list,
-                                                  context,
-                                                  flags_setter,
-                                                  cascade_level);
-
-        SelectorMap::get_matching_rules(element,
-                                        &self.other,
-                                        matching_rules_list,
-                                        context,
-                                        flags_setter,
-                                        cascade_level);
-
-        // Sort only the rules we just added.
-        sort_by_key(&mut matching_rules_list[init_len..],
-                    |block| (block.specificity, block.source_order));
-    }
-
-    /// Append to `rule_list` all universal Rules (rules with selector `*|*`) in
-    /// `self` sorted by specificity and source order.
-    pub fn get_universal_rules(&self,
-                               cascade_level: CascadeLevel)
-                               -> Vec<ApplicableDeclarationBlock> {
-        debug_assert!(!cascade_level.is_important());
-        if self.is_empty() {
-            return vec![];
-        }
-
-        let mut rules_list = vec![];
-        for rule in self.other.iter() {
-            if rule.selector.is_universal() {
-                rules_list.push(rule.to_applicable_declaration_block(cascade_level))
-            }
-        }
-
-        sort_by_key(&mut rules_list,
-                    |block| (block.specificity, block.source_order));
-
-        rules_list
-    }
-
-    fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector, F>(
-        element: &E,
-        hash: &FnvHashMap<Str, Vec<Rule>>,
-        key: &BorrowedStr,
-        matching_rules: &mut Vector,
-        context: &mut MatchingContext,
-        flags_setter: &mut F,
-        cascade_level: CascadeLevel)
-        where E: TElement,
-              Str: Borrow<BorrowedStr> + Eq + Hash,
-              BorrowedStr: Eq + Hash,
-              Vector: VecLike<ApplicableDeclarationBlock>,
-              F: FnMut(&E, ElementSelectorFlags),
-    {
-        if let Some(rules) = hash.get(key) {
-            SelectorMap::get_matching_rules(element,
-                                            rules,
-                                            matching_rules,
-                                            context,
-                                            flags_setter,
-                                            cascade_level)
-        }
-    }
-
-    /// Adds rules in `rules` that match `element` to the `matching_rules` list.
-    fn get_matching_rules<E, V, F>(element: &E,
-                                   rules: &[Rule],
-                                   matching_rules: &mut V,
-                                   context: &mut MatchingContext,
-                                   flags_setter: &mut F,
-                                   cascade_level: CascadeLevel)
-        where E: TElement,
-              V: VecLike<ApplicableDeclarationBlock>,
-              F: FnMut(&E, ElementSelectorFlags),
-    {
-        for rule in rules {
-            if matches_selector(&rule.selector.inner,
-                                element,
-                                context,
-                                flags_setter) {
-                matching_rules.push(
-                    rule.to_applicable_declaration_block(cascade_level));
-            }
-        }
-    }
-}
-
-impl<T> SelectorMap<T> where T: Clone + Borrow<SelectorInner<SelectorImpl>> {
-    /// Inserts into the correct hash, trying id, class, and localname.
-    pub fn insert(&mut self, entry: T) {
-        self.count += 1;
-
-        if let Some(id_name) = get_id_name(entry.borrow()) {
-            find_push(&mut self.id_hash, id_name, entry);
-            return;
-        }
-
-        if let Some(class_name) = get_class_name(entry.borrow()) {
-            find_push(&mut self.class_hash, class_name, entry);
-            return;
-        }
-
-        if let Some(LocalNameSelector { name, lower_name }) = get_local_name(entry.borrow()) {
-            // If the local name in the selector isn't lowercase, insert it into
-            // the rule hash twice. This means that, during lookup, we can always
-            // find the rules based on the local name of the element, regardless
-            // of whether it's an html element in an html document (in which case
-            // we match against lower_name) or not (in which case we match against
-            // name).
-            //
-            // In the case of a non-html-element-in-html-document with a
-            // lowercase localname and a non-lowercase selector, the rulehash
-            // lookup may produce superfluous selectors, but the subsequent
-            // selector matching work will filter them out.
-            if name != lower_name {
-                find_push(&mut self.local_name_hash, lower_name, entry.clone());
-            }
-            find_push(&mut self.local_name_hash, name, entry);
-
-            return;
-        }
-
-        self.other.push(entry);
-    }
-
-    /// Looks up entries by id, class, local name, and other (in order).
-    ///
-    /// Each entry is passed to the callback, which returns true to continue
-    /// iterating entries, or false to terminate the lookup.
-    ///
-    /// Returns false if the callback ever returns false.
-    ///
-    /// FIXME(bholley) This overlaps with SelectorMap<Rule>::get_all_matching_rules,
-    /// but that function is extremely hot and I'd rather not rearrange it.
-    #[inline]
-    pub fn lookup<E, F>(&self, element: E, f: &mut F) -> bool
-        where E: TElement,
-              F: FnMut(&T) -> bool
-    {
-        // Id.
-        if let Some(id) = element.get_id() {
-            if let Some(v) = self.id_hash.get(&id) {
-                for entry in v.iter() {
-                    if !f(&entry) {
-                        return false;
-                    }
-                }
-            }
-        }
-
-        // Class.
-        let mut done = false;
-        element.each_class(|class| {
-            if !done {
-                if let Some(v) = self.class_hash.get(class) {
-                    for entry in v.iter() {
-                        if !f(&entry) {
-                            done = true;
-                            return;
-                        }
-                    }
-                }
-            }
-        });
-        if done {
-            return false;
-        }
-
-        // Local name.
-        if let Some(v) = self.local_name_hash.get(element.get_local_name()) {
-            for entry in v.iter() {
-                if !f(&entry) {
-                    return false;
-                }
-            }
-        }
-
-        // Other.
-        for entry in self.other.iter() {
-            if !f(&entry) {
-                return false;
-            }
-        }
-
-        true
-    }
-
-    /// Performs a normal lookup, and also looks up entries for the passed-in
-    /// id and classes.
-    ///
-    /// Each entry is passed to the callback, which returns true to continue
-    /// iterating entries, or false to terminate the lookup.
-    ///
-    /// Returns false if the callback ever returns false.
-    #[inline]
-    pub fn lookup_with_additional<E, F>(&self,
-                                        element: E,
-                                        additional_id: Option<Atom>,
-                                        additional_classes: &[Atom],
-                                        f: &mut F)
-                                        -> bool
-        where E: TElement,
-              F: FnMut(&T) -> bool
-    {
-        // Do the normal lookup.
-        if !self.lookup(element, f) {
-            return false;
-        }
-
-        // Check the additional id.
-        if let Some(id) = additional_id {
-            if let Some(v) = self.id_hash.get(&id) {
-                for entry in v.iter() {
-                    if !f(&entry) {
-                        return false;
-                    }
-                }
-            }
-        }
-
-        // Check the additional classes.
-        for class in additional_classes {
-            if let Some(v) = self.class_hash.get(class) {
-                for entry in v.iter() {
-                    if !f(&entry) {
-                        return false;
-                    }
-                }
-            }
-        }
-
-        true
-    }
-}
-
-/// Searches the selector from right to left, beginning to the left of the
-/// ::pseudo-element (if any), and ending at the first combinator.
-///
-/// The first non-None value returned from |f| is returned.
-///
-/// Effectively, pseudo-elements are ignored, given only state pseudo-classes
-/// may appear before them.
-fn find_from_right<F, R>(selector: &SelectorInner<SelectorImpl>, mut f: F) -> Option<R>
-    where F: FnMut(&Component<SelectorImpl>) -> Option<R>,
-{
-    let mut iter = selector.complex.iter();
-    for ss in &mut iter {
-        if let Some(r) = f(ss) {
-            return Some(r)
-        }
-    }
-
-    if iter.next_sequence() == Some(Combinator::PseudoElement) {
-        for ss in &mut iter {
-            if let Some(r) = f(ss) {
-                return Some(r)
-            }
-        }
-    }
-
-    None
-}
-
-/// Retrieve the first ID name in the selector, or None otherwise.
-pub fn get_id_name(selector: &SelectorInner<SelectorImpl>) -> Option<Atom> {
-    find_from_right(selector, |ss| {
-        // TODO(pradeep): Implement case-sensitivity based on the
-        // document type and quirks mode.
-        if let Component::ID(ref id) = *ss {
-            return Some(id.clone());
-        }
-        None
-    })
-}
-
-/// Retrieve the FIRST class name in the selector, or None otherwise.
-pub fn get_class_name(selector: &SelectorInner<SelectorImpl>) -> Option<Atom> {
-    find_from_right(selector, |ss| {
-        // TODO(pradeep): Implement case-sensitivity based on the
-        // document type and quirks mode.
-        if let Component::Class(ref class) = *ss {
-            return Some(class.clone());
-        }
-        None
-    })
-}
-
-/// Retrieve the name if it is a type selector, or None otherwise.
-pub fn get_local_name(selector: &SelectorInner<SelectorImpl>)
-                      -> Option<LocalNameSelector<SelectorImpl>> {
-    find_from_right(selector, |ss| {
-        if let Component::LocalName(ref n) = *ss {
-            return Some(LocalNameSelector {
-                name: n.name.clone(),
-                lower_name: n.lower_name.clone(),
-            })
-        }
-        None
-    })
-}
-
 /// A rule, that wraps a style rule, but represents a single selector of the
 /// rule.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Debug)]
 pub struct Rule {
     /// The selector this struct represents. We store this and the
     /// any_{important,normal} booleans inline in the Rule to avoid
     /// pointer-chasing when gathering applicable declarations, which
@@ -1661,29 +1246,33 @@ pub struct Rule {
     pub selector: Selector<SelectorImpl>,
     /// The actual style rule.
     #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
     pub style_rule: Arc<Locked<StyleRule>>,
     /// The source order this style rule appears in.
     pub source_order: usize,
 }
 
-impl Borrow<SelectorInner<SelectorImpl>> for Rule {
-    fn borrow(&self) -> &SelectorInner<SelectorImpl> {
+impl SelectorMapEntry for Rule {
+    fn selector(&self) -> &SelectorInner<SelectorImpl> {
         &self.selector.inner
     }
 }
 
 impl Rule {
     /// Returns the specificity of the rule.
     pub fn specificity(&self) -> u32 {
         self.selector.specificity()
     }
 
-    fn to_applicable_declaration_block(&self, level: CascadeLevel) -> ApplicableDeclarationBlock {
+    /// Turns this rule into an `ApplicableDeclarationBlock` for the given
+    /// cascade level.
+    pub fn to_applicable_declaration_block(&self,
+                                           level: CascadeLevel)
+                                           -> ApplicableDeclarationBlock {
         ApplicableDeclarationBlock {
             source: StyleSource::Style(self.style_rule.clone()),
             level: level,
             source_order: self.source_order,
             specificity: self.specificity(),
         }
     }
 
@@ -1730,15 +1319,8 @@ impl ApplicableDeclarationBlock {
         ApplicableDeclarationBlock {
             source: StyleSource::Declarations(declarations),
             level: level,
             source_order: 0,
             specificity: 0,
         }
     }
 }
-
-#[inline]
-fn find_push<Str: Eq + Hash, V>(map: &mut FnvHashMap<Str, Vec<V>>,
-                                key: Str,
-                                value: V) {
-    map.entry(key).or_insert_with(Vec::new).push(value)
-}
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -1,15 +1,15 @@
 /* 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/. */
 
 //! Traversing the DOM tree; the bloom filter.
 
-use atomic_refcell::{AtomicRefCell, AtomicRefMut};
+use atomic_refcell::AtomicRefCell;
 use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
 use data::{ElementData, ElementStyles, StoredRestyleHint};
 use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode};
 use matching::{ChildCascadeRequirement, MatchMethods};
 use restyle_hints::{HintComputationContext, RestyleHint};
 use selector_parser::RestyleDamage;
 use sharing::StyleSharingBehavior;
 #[cfg(feature = "servo")] use servo_config::opts;
@@ -600,37 +600,37 @@ pub fn resolve_style<E, F, G, H>(context
 
 /// Calculates the style for a single node.
 #[inline]
 #[allow(unsafe_code)]
 pub fn recalc_style_at<E, D>(traversal: &D,
                              traversal_data: &PerLevelTraversalData,
                              context: &mut StyleContext<E>,
                              element: E,
-                             mut data: &mut AtomicRefMut<ElementData>)
+                             data: &mut ElementData)
     where E: TElement,
           D: DomTraversal<E>
 {
-    context.thread_local.begin_element(element, &data);
+    context.thread_local.begin_element(element, data);
     context.thread_local.statistics.elements_traversed += 1;
     debug_assert!(!element.has_snapshot() || element.handled_snapshot(),
                   "Should've handled snapshots here already");
     debug_assert!(data.get_restyle().map_or(true, |r| {
         !r.has_sibling_invalidations()
     }), "Should've computed the final hint and handled later_siblings already");
 
     let compute_self = !element.has_current_styles(data);
     let mut inherited_style_changed = false;
 
     debug!("recalc_style_at: {:?} (compute_self={:?}, dirty_descendants={:?}, data={:?})",
            element, compute_self, element.has_dirty_descendants(), data);
 
     // Compute style for this element if necessary.
     if compute_self {
-        match compute_style(traversal, traversal_data, context, element, &mut data) {
+        match compute_style(traversal, traversal_data, context, element, data) {
             ChildCascadeRequirement::MustCascade => {
                 inherited_style_changed = true;
             }
             ChildCascadeRequirement::CanSkipCascade => {}
         };
 
         // If we're restyling this element to display:none, throw away all style
         // data in the subtree, notify the caller to early-return.
@@ -723,31 +723,32 @@ pub fn recalc_style_at<E, D>(traversal: 
         unsafe { element.unset_dirty_descendants(); }
     }
 }
 
 fn compute_style<E, D>(_traversal: &D,
                        traversal_data: &PerLevelTraversalData,
                        context: &mut StyleContext<E>,
                        element: E,
-                       mut data: &mut AtomicRefMut<ElementData>) -> ChildCascadeRequirement
+                       data: &mut ElementData)
+                       -> ChildCascadeRequirement
     where E: TElement,
           D: DomTraversal<E>,
 {
     use data::RestyleKind::*;
     use sharing::StyleSharingResult::*;
 
     context.thread_local.statistics.elements_styled += 1;
     let kind = data.restyle_kind();
 
     // First, try the style sharing cache. If we get a match we can skip the rest
     // of the work.
     if let MatchAndCascade = kind {
         let sharing_result = unsafe {
-            element.share_style_if_possible(context, &mut data)
+            element.share_style_if_possible(context, data)
         };
         if let StyleWasShared(index, had_damage) = sharing_result {
             context.thread_local.statistics.styles_shared += 1;
             context.thread_local.style_sharing_candidate_cache.touch(index);
             return had_damage;
         }
     }
 
@@ -759,32 +760,32 @@ fn compute_style<E, D>(_traversal: &D,
                                               traversal_data.current_dom_depth);
 
             context.thread_local.bloom_filter.assert_complete(element);
             context.thread_local.statistics.elements_matched += 1;
 
             // Perform the matching and cascading.
             element.match_and_cascade(
                 context,
-                &mut data,
+                data,
                 StyleSharingBehavior::Allow
             )
         }
         CascadeWithReplacements(flags) => {
-            let rules_changed = element.replace_rules(flags, context, &mut data);
+            let rules_changed = element.replace_rules(flags, context, data);
             element.cascade_primary_and_pseudos(
                 context,
-                &mut data,
+                data,
                 rules_changed.important_rules_changed()
             )
         }
         CascadeOnly => {
             element.cascade_primary_and_pseudos(
                 context,
-                &mut data,
+                data,
                 /* important_rules_changed = */ false
             )
         }
     }
 }
 
 fn preprocess_children<E, D>(context: &mut StyleContext<E>,
                              parent_traversal_data: &PerLevelTraversalData,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1198,17 +1198,21 @@ fn get_pseudo_style(guard: &SharedRwLock
                     doc_data: &PerDocumentStyleData)
                     -> Option<Arc<ComputedValues>>
 {
     match pseudo.cascade_type() {
         PseudoElementCascadeType::Eager => styles.pseudos.get(&pseudo).map(|s| s.values().clone()),
         PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
         PseudoElementCascadeType::Lazy => {
             let d = doc_data.borrow_mut();
-            let base = styles.primary.values();
+            let base = if pseudo.inherits_from_default_values() {
+                d.default_computed_values()
+            } else {
+                styles.primary.values()
+            };
             let guards = StylesheetGuards::same(guard);
             let metrics = get_metrics_provider_for_product();
             d.stylist.lazily_compute_pseudo_element_style(&guards,
                                                           &element,
                                                           &pseudo,
                                                           base,
                                                           &metrics)
                      .map(|s| s.values().clone())
--- a/servo/tests/unit/style/stylist.rs
+++ b/servo/tests/unit/style/stylist.rs
@@ -8,22 +8,22 @@ use html5ever::LocalName;
 use selectors::parser::LocalName as LocalNameSelector;
 use selectors::parser::Selector;
 use servo_atoms::Atom;
 use style::context::QuirksMode;
 use style::media_queries::{Device, MediaType};
 use style::properties::{PropertyDeclarationBlock, PropertyDeclaration};
 use style::properties::{longhands, Importance};
 use style::rule_tree::CascadeLevel;
+use style::selector_map::{self, SelectorMap};
 use style::selector_parser::{SelectorImpl, SelectorParser};
 use style::shared_lock::SharedRwLock;
 use style::stylearc::Arc;
 use style::stylesheets::StyleRule;
-use style::stylist;
-use style::stylist::{Rule, SelectorMap, Stylist};
+use style::stylist::{Stylist, Rule};
 use style::stylist::needs_revalidation;
 use style::thread_state;
 
 /// Helper method to get some Rules from selector strings.
 /// Each sublist of the result contains the Rules for one StyleRule.
 fn get_mock_rules(css_selectors: &[&str]) -> (Vec<Vec<Rule>>, SharedRwLock) {
     let shared_lock = SharedRwLock::new();
     (css_selectors.iter().enumerate().map(|(i, selectors)| {
@@ -170,32 +170,32 @@ fn test_rule_ordering_same_specificity()
     assert!((a.specificity(), a.source_order) < ((b.specificity(), b.source_order)),
             "The rule that comes later should win.");
 }
 
 
 #[test]
 fn test_get_id_name() {
     let (rules_list, _) = get_mock_rules(&[".intro", "#top"]);
-    assert_eq!(stylist::get_id_name(&rules_list[0][0].selector.inner), None);
-    assert_eq!(stylist::get_id_name(&rules_list[1][0].selector.inner), Some(Atom::from("top")));
+    assert_eq!(selector_map::get_id_name(&rules_list[0][0].selector.inner), None);
+    assert_eq!(selector_map::get_id_name(&rules_list[1][0].selector.inner), Some(Atom::from("top")));
 }
 
 #[test]
 fn test_get_class_name() {
     let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]);
-    assert_eq!(stylist::get_class_name(&rules_list[0][0].selector.inner), Some(Atom::from("foo")));
-    assert_eq!(stylist::get_class_name(&rules_list[1][0].selector.inner), None);
+    assert_eq!(selector_map::get_class_name(&rules_list[0][0].selector.inner), Some(Atom::from("foo")));
+    assert_eq!(selector_map::get_class_name(&rules_list[1][0].selector.inner), None);
 }
 
 #[test]
 fn test_get_local_name() {
     let (rules_list, _) = get_mock_rules(&["img.foo", "#top", "IMG", "ImG"]);
     let check = |i: usize, names: Option<(&str, &str)>| {
-        assert!(stylist::get_local_name(&rules_list[i][0].selector.inner)
+        assert!(selector_map::get_local_name(&rules_list[i][0].selector.inner)
                 == names.map(|(name, lower_name)| LocalNameSelector {
                         name: LocalName::from(name),
                         lower_name: LocalName::from(lower_name) }))
     };
     check(0, Some(("img", "img")));
     check(1, None);
     check(2, Some(("IMG", "img")));
     check(3, Some(("ImG", "img")));
--- a/taskcluster/docker/funsize-balrog-submitter/scripts/funsize-balrog-submitter.py
+++ b/taskcluster/docker/funsize-balrog-submitter/scripts/funsize-balrog-submitter.py
@@ -111,16 +111,17 @@ def main():
     parser.add_argument("-a", "--api-root", required=True,
                         help="Balrog API root")
     parser.add_argument("-d", "--dummy", action="store_true",
                         help="Add '-dummy' suffix to branch name")
     parser.add_argument("--signing-cert", required=True)
     parser.add_argument("-v", "--verbose", action="store_const",
                         dest="loglevel", const=logging.DEBUG,
                         default=logging.INFO)
+    parser.add_argument("--product", help="Override product name from application.ini")
     args = parser.parse_args()
     logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s",
                         level=args.loglevel)
     logging.getLogger("requests").setLevel(logging.WARNING)
     logging.getLogger("boto").setLevel(logging.WARNING)
 
     balrog_username = os.environ.get("BALROG_USERNAME")
     balrog_password = os.environ.get("BALROG_PASSWORD")
@@ -151,18 +152,19 @@ def main():
         }]
 
         if "previousVersion" in e and "previousBuildNumber" in e:
             log.info("Release style balrog submission")
             partial_info[0]["previousVersion"] = e["previousVersion"]
             partial_info[0]["previousBuildNumber"] = e["previousBuildNumber"]
             submitter = ReleaseSubmitterV4(api_root=args.api_root, auth=auth,
                                            dummy=args.dummy)
+            productName = args.product or e["appName"]
             retry(lambda: submitter.run(
-                platform=e["platform"], productName=e["appName"],
+                platform=e["platform"], productName=productName,
                 version=e["toVersion"],
                 build_number=e["toBuildNumber"],
                 appVersion=e["version"], extVersion=e["version"],
                 buildID=e["to_buildid"], locale=e["locale"],
                 hashFunction='sha512',
                 partialInfo=partial_info, completeInfo=complete_info,
             ))
         elif "from_buildid" in e and uploads_enabled:
@@ -186,19 +188,20 @@ def main():
                 s3_bucket, aws_access_key_id, aws_secret_access_key,
                 partial_mar_url, partial_mar_dest, args.signing_cert)
             complete_info[0]["url"] = verify_copy_to_s3(
                 s3_bucket, aws_access_key_id, aws_secret_access_key,
                 complete_mar_url, complete_mar_dest, args.signing_cert)
             partial_info[0]["from_buildid"] = e["from_buildid"]
             submitter = NightlySubmitterV4(api_root=args.api_root, auth=auth,
                                            dummy=args.dummy)
+            productName = args.product or e["appName"]
             retry(lambda: submitter.run(
                 platform=e["platform"], buildID=e["to_buildid"],
-                productName=e["appName"], branch=e["branch"],
+                productName=productName, branch=e["branch"],
                 appVersion=e["version"], locale=e["locale"],
                 hashFunction='sha512', extVersion=e["version"],
                 partialInfo=partial_info, completeInfo=complete_info),
                 attempts=30, sleeptime=10, max_sleeptime=60,
             )
         else:
             raise RuntimeError("Cannot determine Balrog submission style")
 
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -508,17 +508,16 @@ def handle_keyed_by(config, tests):
 
 @transforms.add
 def enable_code_coverage(config, tests):
     """Enable code coverage for the linux64-ccov/opt & linux64-jsdcov/opt build-platforms"""
     for test in tests:
         if test['build-platform'] == 'linux64-ccov/opt':
             test['mozharness'].setdefault('extra-options', []).append('--code-coverage')
             test['instance-size'] = 'xlarge'
-            test['e10s'] = False
             test['run-on-projects'] = []
         elif test['build-platform'] == 'linux64-jsdcov/opt':
             test['run-on-projects'] = []
         yield test
 
 
 @transforms.add
 def handle_run_on_projects(config, tests):
--- a/testing/geckodriver/src/marionette.rs
+++ b/testing/geckodriver/src/marionette.rs
@@ -632,17 +632,17 @@ impl MarionetteSession {
             return Err(WebDriverError::new(status, error.message));
         }
 
         try!(self.update(msg, &resp));
 
         Ok(match msg.command {
             // Everything that doesn't have a response value
             Get(_) | GoBack | GoForward | Refresh | SetTimeouts(_) |
-            SetWindowRect(_) | MaximizeWindow | SwitchToWindow(_) | SwitchToFrame(_) |
+            MaximizeWindow | SwitchToWindow(_) | SwitchToFrame(_) |
             SwitchToParentFrame | AddCookie(_) | DeleteCookies | DeleteCookie(_) |
             DismissAlert | AcceptAlert | SendAlertText(_) | ElementClick(_) |
             ElementTap(_) | ElementClear(_) | ElementSendKeys(_, _) |
             PerformActions(_) | ReleaseActions => {
                 WebDriverResponse::Void
             },
             // Things that simply return the contents of the marionette "value" property
             GetCurrentUrl | GetTitle | GetPageSource | GetWindowHandle | IsDisplayed(_) |
@@ -765,16 +765,47 @@ impl MarionetteSession {
                     try_opt!(resp.result.find("height"),
                              ErrorStatus::UnknownError,
                              "Failed to find height field").as_f64(),
                     ErrorStatus::UnknownError,
                     "Failed to interpret width as float");
 
                 WebDriverResponse::ElementRect(ElementRectResponse::new(x, y, width, height))
             },
+            SetWindowRect(_) => {
+                let x = try_opt!(
+                    try_opt!(resp.result.find("x"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find x field").as_f64(),
+                    ErrorStatus::UnknownError,
+                    "Failed to interpret x as float");
+
+                let y = try_opt!(
+                    try_opt!(resp.result.find("y"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find y field").as_f64(),
+                    ErrorStatus::UnknownError,
+                    "Failed to interpret y as float");
+
+                let width = try_opt!(
+                    try_opt!(resp.result.find("width"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find width field").as_f64(),
+                    ErrorStatus::UnknownError,
+                    "Failed to interpret width as float");
+
+                let height = try_opt!(
+                    try_opt!(resp.result.find("height"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find height field").as_f64(),
+                    ErrorStatus::UnknownError,
+                    "Failed to interpret width as float");
+
+                WebDriverResponse::ElementRect(ElementRectResponse::new(x, y, width, height))
+            },
             GetCookies => {
                 let cookies = try!(self.process_cookies(&resp.result));
                 WebDriverResponse::Cookie(CookieResponse::new(cookies))
             },
             GetNamedCookie(ref name) => {
                 let mut cookies = try!(self.process_cookies(&resp.result));
                 cookies.retain(|x| x.name == *name);
                 WebDriverResponse::Cookie(CookieResponse::new(cookies))
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -635,45 +635,48 @@ var Bookmarks = Object.freeze({
       info = { guid: guidOrInfo };
 
     // Disallow removing the root folders.
     if ([this.rootGuid, this.menuGuid, this.toolbarGuid, this.unfiledGuid,
          this.tagsGuid, this.mobileGuid].includes(info.guid)) {
       throw new Error("It's not possible to remove Places root folders.");
     }
 
+    if (!("source" in options)) {
+      options.source = Bookmarks.SOURCES.DEFAULT;
+    }
+
     // Even if we ignore any other unneeded property, we still validate any
     // known property to reduce likelihood of hidden bugs.
     let removeInfo = validateBookmarkObject(info);
 
     return (async function() {
       let item = await fetchBookmark(removeInfo);
       if (!item)
         throw new Error("No bookmarks found for the provided GUID.");
 
       item = await removeBookmark(item, options);
 
       // Notify onItemRemoved to listeners.
-      let { source = Bookmarks.SOURCES.DEFAULT } = options;
       let observers = PlacesUtils.bookmarks.getObservers();
       let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
       let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
       notify(observers, "onItemRemoved", [ item._id, item._parentId, item.index,
                                            item.type, uri, item.guid,
                                            item.parentGuid,
-                                           source ],
+                                           options.source ],
                                          { isTagging: isUntagging });
 
       if (isUntagging) {
         for (let entry of (await fetchBookmarksByURL(item))) {
           notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                                PlacesUtils.toPRTime(entry.lastModified),
                                                entry.type, entry._parentId,
                                                entry.guid, entry.parentGuid,
-                                               "", source ]);
+                                               "", options.source ]);
         }
       }
 
       // Remove non-enumerable properties.
       return Object.assign({}, item);
     })();
   },
 
@@ -686,16 +689,20 @@ var Bookmarks = Object.freeze({
    *        Additional options. Currently supports the following properties:
    *         - source: The change source, forwarded to all bookmark observers.
    *           Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
    *
    * @return {Promise} resolved when the removal is complete.
    * @resolves once the removal is complete.
    */
   eraseEverything(options = {}) {
+    if (!options.source) {
+      options.source = Bookmarks.SOURCES.DEFAULT;
+    }
+
     const folderGuids = [this.toolbarGuid, this.menuGuid, this.unfiledGuid,
                           this.mobileGuid];
     return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: eraseEverything",
       db => db.executeTransaction(async function() {
         await removeFoldersContents(db, folderGuids, options);
         const time = PlacesUtils.toPRTime(new Date());
         const syncChangeDelta =
           PlacesSyncUtils.bookmarks.determineSyncChangeDelta(options.source);
@@ -773,16 +780,19 @@ var Bookmarks = Object.freeze({
    *           promise is resolved to null.
    * @rejects if an error happens while fetching.
    * @throws if the arguments are invalid.
    *
    * @note Any unknown property in the info object is ignored.  Known properties
    *       may be overwritten.
    */
   fetch(guidOrInfo, onResult = null, options = {}) {
+    if (!("concurrent" in options)) {
+      options.concurrent = false;
+    }
     if (onResult && typeof onResult != "function")
       throw new Error("onResult callback must be a valid function");
     let info = guidOrInfo;
     if (!info)
       throw new Error("Input should be a valid object");
     if (typeof(info) != "object") {
       info = { guid: guidOrInfo };
     } else if (Object.keys(info).length == 1) {
@@ -942,16 +952,20 @@ var Bookmarks = Object.freeze({
     if (!Array.isArray(orderedChildrenGuids) || !orderedChildrenGuids.length)
       throw new Error("Must provide a sorted array of children GUIDs.");
     try {
       orderedChildrenGuids.forEach(PlacesUtils.BOOKMARK_VALIDATORS.guid);
     } catch (ex) {
       throw new Error("Invalid GUID found in the sorted children array.");
     }
 
+    if (!("source" in options)) {
+      options.source = Bookmarks.SOURCES.DEFAULT;
+    }
+
     return (async () => {
       let parent = await fetchBookmark(info);
       if (!parent || parent.type != this.TYPE_FOLDER)
         throw new Error("No folder found for the provided GUID.");
 
       let sortedChildren = await reorderChildren(parent, orderedChildrenGuids,
                                                  options);
 
@@ -2153,9 +2167,8 @@ function adjustSeparatorsSyncCounter(db,
     `,
     {
       delta: syncChangeDelta,
       parent: parentId,
       start_index: startIndex,
       item_type: Bookmarks.TYPE_SEPARATOR
     });
 }
-
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -909,17 +909,17 @@ var annotateOrphan = async function(item
   let itemId = await PlacesUtils.promiseItemId(guid);
   PlacesUtils.annotations.setItemAnnotation(itemId,
     BookmarkSyncUtils.SYNC_PARENT_ANNO, requestedParentSyncId, 0,
     PlacesUtils.annotations.EXPIRE_NEVER,
     SOURCE_SYNC);
 };
 
 var reparentOrphans = async function(item) {
-  if (item.kind != BookmarkSyncUtils.KINDS.FOLDER) {
+  if (!item.kind || item.kind != BookmarkSyncUtils.KINDS.FOLDER) {
     return;
   }
   let orphanGuids = await fetchGuidsWithAnno(BookmarkSyncUtils.SYNC_PARENT_ANNO,
                                              item.syncId);
   let folderGuid = BookmarkSyncUtils.syncIdToGuid(item.syncId);
   BookmarkSyncLog.debug(`reparentOrphans: Reparenting ${
     JSON.stringify(orphanGuids)} to ${item.syncId}`);
   for (let i = 0; i < orphanGuids.length; ++i) {
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -2271,34 +2271,39 @@ var Keywords = {
    * Adds a new keyword and postData for the given URL.
    *
    * @param keywordEntry
    *        An object describing the keyword to insert, in the form:
    *        {
    *          keyword: non-empty string,
    *          URL: URL or href to associate to the keyword,
    *          postData: optional POST data to associate to the keyword
+   *          source: The change source, forwarded to all bookmark observers.
+   *            Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
    *        }
    * @note Do not define a postData property if there isn't any POST data.
    * @resolves when the addition is complete.
    */
   insert(keywordEntry) {
     if (!keywordEntry || typeof keywordEntry != "object")
       throw new Error("Input should be a valid object");
 
     if (!("keyword" in keywordEntry) || !keywordEntry.keyword ||
         typeof(keywordEntry.keyword) != "string")
       throw new Error("Invalid keyword");
     if (("postData" in keywordEntry) && keywordEntry.postData &&
                                         typeof(keywordEntry.postData) != "string")
       throw new Error("Invalid POST data");
     if (!("url" in keywordEntry))
       throw new Error("undefined is not a valid URL");
-    let { keyword, url,
-          source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } = keywordEntry;
+
+    if (!("source" in keywordEntry)) {
+      keywordEntry.source = PlacesUtils.bookmarks.SOURCES.DEFAULT;
+    }
+    let { keyword, url, source } = keywordEntry;
     keyword = keyword.trim().toLowerCase();
     let postData = keywordEntry.postData || null;
     // This also checks href for validity
     url = new URL(url);
 
     return PlacesUtils.withConnectionWrapper("Keywords.insert", async function(db) {
         let cache = await gKeywordsCachePromise;
 
@@ -2353,18 +2358,22 @@ var Keywords = {
    * Removes a keyword.
    *
    * @param keyword
    *        The keyword to remove.
    * @return {Promise}
    * @resolves when the removal is complete.
    */
   remove(keywordOrEntry) {
-    if (typeof(keywordOrEntry) == "string")
-      keywordOrEntry = { keyword: keywordOrEntry };
+    if (typeof(keywordOrEntry) == "string") {
+      keywordOrEntry = {
+        keyword: keywordOrEntry,
+        source: Ci.nsINavBookmarksService.SOURCE_DEFAULT
+      };
+    }
 
     if (keywordOrEntry === null || typeof(keywordOrEntry) != "object" ||
         !keywordOrEntry.keyword || typeof keywordOrEntry.keyword != "string")
       throw new Error("Invalid keyword");
 
     let { keyword,
           source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } = keywordOrEntry;
     keyword = keywordOrEntry.keyword.trim().toLowerCase();
--- a/toolkit/components/places/tests/unit/test_async_transactions.js
+++ b/toolkit/components/places/tests/unit/test_async_transactions.js
@@ -192,17 +192,21 @@ function ensureItemsRemoved(...items) {
 }
 
 function ensureItemsChanged(...items) {
   for (let item of items) {
     do_check_true(observer.itemsChanged.has(item.guid));
     let changes = observer.itemsChanged.get(item.guid);
     do_check_true(changes.has(item.property));
     let info = changes.get(item.property);
-    do_check_eq(info.isAnnoProperty, Boolean(item.isAnnoProperty));
+    if (!("isAnnoProperty" in item)) {
+      do_check_false(info.isAnnoProperty);
+    } else {
+      do_check_eq(info.isAnnoProperty, Boolean(item.isAnnoProperty));
+    }
     do_check_eq(info.newValue, item.newValue);
     if ("url" in item)
       do_check_true(item.url.equals(info.url));
   }
 }
 
 function ensureAnnotationsSet(aGuid, aAnnos) {
   do_check_true(observer.itemsChanged.has(aGuid));
--- a/toolkit/components/places/tests/unit/test_hosts_triggers.js
+++ b/toolkit/components/places/tests/unit/test_hosts_triggers.js
@@ -79,17 +79,17 @@ var urls = [{uri: NetUtil.newURI("http:/
            ];
 
 const NEW_URL = "http://different.mozilla.org/";
 
 add_task(async function test_moz_hosts_update() {
   let places = [];
   urls.forEach(function(url) {
     let place = { uri: url.uri,
-                  title: "test for " + url.url,
+                  title: "test for " + url.uri.spec,
                   transition: url.typed ? TRANSITION_TYPED : undefined };
     places.push(place);
   });
 
   await PlacesTestUtils.addVisits(places);
 
   checkHostInMozHosts(urls[0].uri, urls[0].typed, urls[0].prefix);
   checkHostInMozHosts(urls[1].uri, urls[1].typed, urls[1].prefix);
--- a/toolkit/components/places/tests/unit/test_mozIAsyncLivemarks.js
+++ b/toolkit/components/places/tests/unit/test_mozIAsyncLivemarks.js
@@ -138,17 +138,17 @@ add_task(async function test_addLivemark
   } catch (ex) {
     do_check_eq(ex.result, Cr.NS_ERROR_INVALID_ARG);
   }
 });
 
 add_task(async function test_addLivemark_badGuid_throws() {
   try {
     await PlacesUtils.livemarks.addLivemark(
-      { parentGuid: PlacesUtils.bookmarks.unfileGuid
+      { parentGuid: PlacesUtils.bookmarks.unfiledGuid
       , feedURI: FEED_URI
       , guid: "123456" });
     do_throw("Invoking addLivemark with a bad guid should throw");
   } catch (ex) {
     do_check_eq(ex.result, Cr.NS_ERROR_INVALID_ARG);
   }
 });
 
--- a/toolkit/components/places/tests/unit/test_promiseBookmarksTree.js
+++ b/toolkit/components/places/tests/unit/test_promiseBookmarksTree.js
@@ -165,16 +165,20 @@ async function new_folder(aInfo) {
 // Walks a result nodes tree and test promiseBookmarksTree for each node.
 // DO NOT COPY THIS LOGIC:  It is done here to accomplish a more comprehensive
 // test of the API (the entire hierarchy data is available in the very test).
 async function test_promiseBookmarksTreeForEachNode(aNode, aOptions, aExcludedGuids) {
   Assert.ok(aNode.bookmarkGuid && aNode.bookmarkGuid.length > 0);
   let item = await PlacesUtils.promiseBookmarksTree(aNode.bookmarkGuid, aOptions);
   await compareToNode(item, aNode, true, aExcludedGuids);
 
+  if (!PlacesUtils.nodeIsContainer(aNode)) {
+    return item;
+  }
+
   for (let i = 0; i < aNode.childCount; i++) {
     let child = aNode.getChild(i);
     if (child.itemId != PlacesUtils.tagsFolderId)
       await test_promiseBookmarksTreeForEachNode(child,
                                                  { includeItemIds: true },
                                                  aExcludedGuids);
   }
   return item;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -6002,26 +6002,16 @@
     "low": 1000,
     "high": 64000,
     "n_buckets": 7,
     "bug_numbers": [1301104],
     "alert_emails": ["mconley@mozilla.com"],
     "releaseChannelCollection": "opt-out",
     "description": "Firefox: If the spinner interstitial displays during tab switching, records the time in ms the graphic is visible. This probe is similar to FX_TAB_SWITCH_SPINNER_VISIBLE_MS, but is for truly degenerate cases."
   },
-  "FX_TAB_SWITCH_SPINNER_TYPE": {
-    "record_in_processes": ["main", "content"],
-    "expires_in_version": "60",
-    "kind": "categorical",
-    "bug_numbers": [1301104],
-    "alert_emails": ["mconley@mozilla.com"],
-    "releaseChannelCollection": "opt-out",
-    "description": "Firefox: If the spinner interstitial displays during tab switching, record if the tab being switched to has been seen (content was visible) before. If not, record if the tab is 'old' (> 1000ms since creation) or 'new'.",
-    "labels": ["seen", "unseenOld", "unseenNew"]
-  },
   "FX_TAB_CLICK_MS": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "default",
     "kind": "exponential",
     "high": 1000,
     "n_buckets": 20,
     "description": "Firefox: Time in ms spent on switching tabs in response to a tab click"
   },
--- a/toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp
+++ b/toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp
@@ -7,20 +7,24 @@
 #include <cstring>
 #include <string>
 
 #include <dlfcn.h>
 #include <unistd.h>
 
 #include "third_party/curl/curl.h"
 
+#include "mozilla/Unused.h"
+
 namespace PingSender {
 
 using std::string;
 
+using mozilla::Unused;
+
 /**
  * A simple wrapper around libcurl "easy" functions. Provides RAII opening
  * and initialization of the curl library
  */
 class CurlWrapper
 {
 public:
   CurlWrapper();
@@ -130,21 +134,33 @@ CurlWrapper::Init()
   if (!mCurl) {
     PINGSENDER_LOG("ERROR: Could not initialize libcurl\n");
     return false;
   }
 
   return true;
 }
 
+static size_t
+DummyWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+  Unused << ptr;
+  Unused << size;
+  Unused << nmemb;
+  Unused << userdata;
+
+  return size * nmemb;
+}
+
 bool
 CurlWrapper::Post(const string& url, const string& payload)
 {
   easy_setopt(mCurl, CURLOPT_URL, url.c_str());
   easy_setopt(mCurl, CURLOPT_USERAGENT, kUserAgent);
+  easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, DummyWriteCallback);
 
   // Build the date header.
   std::string dateHeader = GenerateDateHeader();
 
   // Set the custom headers.
   curl_slist* headerChunk = nullptr;
   headerChunk = slist_append(headerChunk, kCustomVersionHeader);
   headerChunk = slist_append(headerChunk, kContentEncodingHeader);
--- a/toolkit/themes/linux/global/global.css
+++ b/toolkit/themes/linux/global/global.css
@@ -120,17 +120,17 @@ statusbarpanel {
 .statusbarpanel-iconic-text,
 .statusbarpanel-menu-iconic {
   padding: 0 1px;
 }
 
 /* XXXBlake yeah, shoot me -- these don't belong here.  I'll move them later. */
 
 sidebarheader {
-  height: 25px;
+  min-height: 25px;
   background-color: -moz-Dialog;
 }
 
 sidebarheader > label {
   padding-inline-start: 4px;
 }
 
 .toolbar-focustarget {
--- a/toolkit/themes/windows/global/global.css
+++ b/toolkit/themes/windows/global/global.css
@@ -129,17 +129,17 @@ statusbarpanel:not(.statusbar-resizerpan
 .statusbarpanel-iconic-text,
 .statusbarpanel-menu-iconic {
   padding: 0 1px;
 }
 
 /* XXXBlake yeah, shoot me -- these don't belong here.  I'll move them later. */
 
 sidebarheader {
-  height: 25px;
+  min-height: 25px;
   background-color: -moz-Dialog;
   -moz-appearance: toolbox;
   border-bottom: 1px solid ThreeDShadow;
   border-top: 1px solid ThreeDHighlight;
 }
 
 sidebarheader > label {
   padding-inline-start: 4px;