Merge mozilla-central to inbound. a=merge CLOSED TREE
authorCosmin Sabou <csabou@mozilla.com>
Fri, 20 Apr 2018 01:38:32 +0300
changeset 785466 afbc50ccf0d05fddfae15ae0e5f678a5c39b1d03
parent 785465 d6cbfda78737876618f2a855df465dd5aa711139 (current diff)
parent 785370 5d73549d363f2a52854ff43fabac9c6fd05b90b0 (diff)
child 785467 95c161a609380242452eefccf7b937caea006e71
push id107236
push userbmo:emilio@crisal.io
push dateFri, 20 Apr 2018 08:31:50 +0000
reviewersmerge
milestone61.0a1
Merge mozilla-central to inbound. a=merge CLOSED TREE
browser/locales/searchplugins/images/yahoo.ico
browser/locales/searchplugins/yahoo-NO.xml
browser/locales/searchplugins/yahoo-answer-zh-TW.xml
browser/locales/searchplugins/yahoo-ar.xml
browser/locales/searchplugins/yahoo-bid-zh-TW.xml
browser/locales/searchplugins/yahoo-br.xml
browser/locales/searchplugins/yahoo-ch.xml
browser/locales/searchplugins/yahoo-cl.xml
browser/locales/searchplugins/yahoo-de.xml
browser/locales/searchplugins/yahoo-en-CA.xml
browser/locales/searchplugins/yahoo-en-GB.xml
browser/locales/searchplugins/yahoo-es.xml
browser/locales/searchplugins/yahoo-espanol.xml
browser/locales/searchplugins/yahoo-fi.xml
browser/locales/searchplugins/yahoo-france.xml
browser/locales/searchplugins/yahoo-fy-NL.xml
browser/locales/searchplugins/yahoo-id.xml
browser/locales/searchplugins/yahoo-in.xml
browser/locales/searchplugins/yahoo-it.xml
browser/locales/searchplugins/yahoo-mx.xml
browser/locales/searchplugins/yahoo-sv-SE.xml
browser/locales/searchplugins/yahoo-tl.xml
browser/locales/searchplugins/yahoo-zh-TW-HK.xml
browser/locales/searchplugins/yahoo-zh-TW.xml
browser/locales/searchplugins/yahoo.xml
browser/modules/RecentWindow.jsm
browser/modules/UpdateTopLevelContentWindowIDHelper.jsm
browser/modules/offlineAppCache.jsm
layout/style/Makefile.in
layout/style/PythonCSSProps.h
layout/style/nsCSSPropertyID.h
mobile/locales/searchplugins/yahoo-br.xml
mobile/locales/searchplugins/yahoo-ch.xml
mobile/locales/searchplugins/yahoo-cl.xml
mobile/locales/searchplugins/yahoo-de.xml
mobile/locales/searchplugins/yahoo-en-GB.xml
mobile/locales/searchplugins/yahoo-es.xml
mobile/locales/searchplugins/yahoo-espanol.xml
mobile/locales/searchplugins/yahoo-fi.xml
mobile/locales/searchplugins/yahoo-france.xml
mobile/locales/searchplugins/yahoo-id.xml
mobile/locales/searchplugins/yahoo-in.xml
mobile/locales/searchplugins/yahoo-it.xml
mobile/locales/searchplugins/yahoo-mx.xml
mobile/locales/searchplugins/yahoo.xml
security/manager/ssl/tests/unit/test_getchain.js
security/manager/ssl/tests/unit/test_getchain/ca-1.pem
security/manager/ssl/tests/unit/test_getchain/ca-1.pem.certspec
security/manager/ssl/tests/unit/test_getchain/ca-2.pem
security/manager/ssl/tests/unit/test_getchain/ca-2.pem.certspec
security/manager/ssl/tests/unit/test_getchain/ee.pem
security/manager/ssl/tests/unit/test_getchain/ee.pem.certspec
security/manager/ssl/tests/unit/test_getchain/moz.build
testing/mozharness/mozharness/mozilla/testing/per_test_base.py
testing/mozharness/mozharness/mozilla/testing/verify_tools.py
--- a/browser/base/content/browser-captivePortal.js
+++ b/browser/base/content/browser-captivePortal.js
@@ -107,17 +107,17 @@ var CaptivePortalWatcher = {
     }
   },
 
   _captivePortalDetected() {
     if (this._delayedCaptivePortalDetectedInProgress) {
       return;
     }
 
-    let win = RecentWindow.getMostRecentBrowserWindow();
+    let win = BrowserWindowTracker.getTopWindow();
     // If no browser window has focus, open and show the tab when we regain focus.
     // This is so that if a different application was focused, when the user
     // (re-)focuses a browser window, we open the tab immediately in that window
     // so they can log in before continuing to browse.
     if (win != Services.ww.activeWindow) {
       this._delayedCaptivePortalDetectedInProgress = true;
       Services.obs.addObserver(this, "xul-window-visible");
     }
@@ -130,17 +130,17 @@ var CaptivePortalWatcher = {
    * doesn't have focus. Triggers a portal recheck to reaffirm state, and adds
    * the tab if needed after a short delay to allow the recheck to complete.
    */
   _delayedCaptivePortalDetected() {
     if (!this._delayedCaptivePortalDetectedInProgress) {
       return;
     }
 
-    let win = RecentWindow.getMostRecentBrowserWindow();
+    let win = BrowserWindowTracker.getTopWindow();
     if (win != Services.ww.activeWindow) {
       // The window that got focused was not a browser window.
       return;
     }
     Services.obs.removeObserver(this, "xul-window-visible");
     this._delayedCaptivePortalDetectedInProgress = false;
 
     if (win != window) {
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -1266,19 +1266,21 @@ BrowserPageActions.shareURL = {
   },
 
   onPlacedInPanel(buttonNode) {
     let action = PageActions.actionForID("shareURL");
     BrowserPageActions.takeActionTitleFromPanel(action);
   },
 
   onShowingSubview(panelViewNode) {
+    let bodyNode = panelViewNode.querySelector(".panel-subview-body");
+
     // We cache the providers + the UI if the user selects the share
     // panel multiple times while the panel is open.
-    if (this._cached) {
+    if (this._cached && bodyNode.childNodes.length > 0) {
       return;
     }
 
     let sharingService = this._sharingService;
     let url = gBrowser.selectedBrowser.currentURI;
     let currentURI = gURLBar.makeURIReadable(url).displaySpec;
     let shareProviders = sharingService.getSharingProviders(currentURI);
     let fragment = document.createDocumentFragment();
@@ -1296,17 +1298,16 @@ BrowserPageActions.shareURL = {
           sharingService.shareUrl(shareTitle, currentURI);
         }
         PanelMultiView.hidePopup(BrowserPageActions.panelNode);
       });
 
       fragment.appendChild(item);
     });
 
-    let bodyNode = panelViewNode.querySelector(".panel-subview-body");
     while (bodyNode.firstChild) {
       bodyNode.firstChild.remove();
     }
     bodyNode.appendChild(fragment);
     this._cached = true;
   }
 };
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -15,16 +15,17 @@ const {WebExtensionPolicy} = Cu.getGloba
 
 // lazy module getters
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AboutHome: "resource:///modules/AboutHome.jsm",
   BrowserUITelemetry: "resource:///modules/BrowserUITelemetry.jsm",
   BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
   BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   CharsetMenu: "resource://gre/modules/CharsetMenu.jsm",
   Color: "resource://gre/modules/Color.jsm",
   ContentSearch: "resource:///modules/ContentSearch.jsm",
   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
   CustomizableUI: "resource:///modules/CustomizableUI.jsm",
   Deprecated: "resource://gre/modules/Deprecated.jsm",
   DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
   E10SUtils: "resource://gre/modules/E10SUtils.jsm",
@@ -43,17 +44,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   PanelMultiView: "resource:///modules/PanelMultiView.jsm",
   PanelView: "resource:///modules/PanelMultiView.jsm",
   PluralForm: "resource://gre/modules/PluralForm.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
   ReaderMode: "resource://gre/modules/ReaderMode.jsm",
   ReaderParent: "resource:///modules/ReaderParent.jsm",
-  RecentWindow: "resource:///modules/RecentWindow.jsm",
   SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
   Sanitizer: "resource:///modules/Sanitizer.jsm",
   SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
   SchedulePressure: "resource:///modules/SchedulePressure.jsm",
   ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm",
   SimpleServiceDiscovery: "resource://gre/modules/SimpleServiceDiscovery.jsm",
   SitePermissions: "resource:///modules/SitePermissions.jsm",
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
@@ -1228,16 +1228,17 @@ var gBrowserInit = {
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIWebNavigation)
           .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
           .QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIXULWindow)
           .XULBrowserWindow = window.XULBrowserWindow;
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
       new nsBrowserAccess();
+    BrowserWindowTracker.track(window);
 
     let initBrowser = gBrowser.initialBrowser;
 
     // remoteType and sameProcessAsFrameLoader are passed through to
     // updateBrowserRemoteness as part of an options object, which itself defaults
     // to an empty object. So defaulting them to undefined here will cause the
     // default behavior in updateBrowserRemoteness if they don't get set.
     let isRemote = gMultiProcessBrowser;
@@ -1484,19 +1485,16 @@ var gBrowserInit = {
       document.getElementById("textfieldDirection-swap").hidden = false;
     }
 
     // Setup click-and-hold gestures access to the session history
     // menus if global click-and-hold isn't turned on
     if (!getBoolPref("ui.click_hold_context_menus", false))
       SetClickAndHoldHandlers();
 
-    ChromeUtils.import("resource:///modules/UpdateTopLevelContentWindowIDHelper.jsm", {})
-      .trackBrowserWindow(window);
-
     PlacesToolbarHelper.init();
 
     ctrlTab.readPref();
     Services.prefs.addObserver(ctrlTab.prefName, ctrlTab);
 
     // The object handling the downloads indicator is initialized here in the
     // delayed startup function, but the actual indicator element is not loaded
     // unless there are downloads to be displayed.
@@ -2670,17 +2668,17 @@ async function BrowserViewSourceOfDocume
     // URL.
     preferredRemoteType =
       E10SUtils.getRemoteTypeForURI(args.URL, gMultiProcessBrowser);
   }
 
   // In the case of popups, we need to find a non-popup browser window.
   if (!tabBrowser || !window.toolbar.visible) {
     // This returns only non-popup browser windows by default.
-    let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+    let browserWindow = BrowserWindowTracker.getTopWindow();
     tabBrowser = browserWindow.gBrowser;
   }
 
   // `viewSourceInBrowser` will load the source content from the page
   // descriptor for the tab (when possible) or fallback to the network if
   // that fails.  Either way, the view source module will manage the tab's
   // location, so use "about:blank" here to avoid unnecessary redundant
   // requests.
@@ -5296,17 +5294,17 @@ nsBrowserAccess.prototype = {
                    aOpenerWindow = null, aOpenerBrowser = null,
                    aTriggeringPrincipal = null, aNextTabParentId = 0, aName = "") {
     let win, needToFocusWin;
 
     // try the current window.  if we're in a popup, fall back on the most recent browser window
     if (window.toolbar.visible)
       win = window;
     else {
-      win = RecentWindow.getMostRecentBrowserWindow({private: aIsPrivate});
+      win = BrowserWindowTracker.getTopWindow({private: aIsPrivate});
       needToFocusWin = true;
     }
 
     if (!win) {
       // we couldn't find a suitable window, a new one needs to be opened.
       return null;
     }
 
@@ -7324,1123 +7322,16 @@ function ReportFalseDeceptiveSite() {
  * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased.
  */
 function formatURL(aFormat, aIsPref) {
   return aIsPref ? Services.urlFormatter.formatURLPref(aFormat) :
                    Services.urlFormatter.formatURL(aFormat);
 }
 
 /**
- * Utility object to handle manipulations of the identity indicators in the UI
- */
-var gIdentityHandler = {
-  /**
-   * nsIURI for which the identity UI is displayed. This has been already
-   * processed by nsIURIFixup.createExposableURI.
-   */
-  _uri: null,
-
-  /**
-   * We only know the connection type if this._uri has a defined "host" part.
-   *
-   * These URIs, like "about:", "file:" and "data:" URIs, will usually be treated as a
-   * an unknown connection.
-   */
-  _uriHasHost: false,
-
-  /**
-   * If this tab belongs to a WebExtension, contains its WebExtensionPolicy.
-   */
-  _pageExtensionPolicy: null,
-
-  /**
-   * Whether this._uri refers to an internally implemented browser page.
-   *
-   * Note that this is set for some "about:" pages, but general "chrome:" URIs
-   * are not included in this category by default.
-   */
-  _isSecureInternalUI: false,
-
-  /**
-   * nsISSLStatus metadata provided by gBrowser.securityUI the last time the
-   * identity UI was updated, or null if the connection is not secure.
-   */
-  _sslStatus: null,
-
-  /**
-   * Bitmask provided by nsIWebProgressListener.onSecurityChange.
-   */
-  _state: 0,
-
-  /**
-   * This flag gets set if the identity popup was opened by a keypress,
-   * to be able to focus it on the popupshown event.
-   */
-  _popupTriggeredByKeyboard: false,
-
-  /**
-   * RegExp used to decide if an about url should be shown as being part of
-   * the browser UI.
-   */
-  _secureInternalUIWhitelist: /^(?:accounts|addons|cache|config|crashes|customizing|downloads|healthreport|license|permissions|preferences|rights|searchreset|sessionrestore|support|welcomeback)(?:[?#]|$)/i,
-
-  get _isBroken() {
-    return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
-  },
-
-  get _isSecure() {
-    // If a <browser> is included within a chrome document, then this._state
-    // will refer to the security state for the <browser> and not the top level
-    // document. In this case, don't upgrade the security state in the UI
-    // with the secure state of the embedded <browser>.
-    return !this._isURILoadedFromFile && this._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
-  },
-
-  get _isEV() {
-    // If a <browser> is included within a chrome document, then this._state
-    // will refer to the security state for the <browser> and not the top level
-    // document. In this case, don't upgrade the security state in the UI
-    // with the EV state of the embedded <browser>.
-    return !this._isURILoadedFromFile && this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
-  },
-
-  get _isMixedActiveContentLoaded() {
-    return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
-  },
-
-  get _isMixedActiveContentBlocked() {
-    return this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
-  },
-
-  get _isMixedPassiveContentLoaded() {
-    return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
-  },
-
-  get _isCertUserOverridden() {
-    return this._state & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN;
-  },
-
-  get _isCertDistrustImminent() {
-    return this._state & Ci.nsIWebProgressListener.STATE_CERT_DISTRUST_IMMINENT;
-  },
-
-  get _hasInsecureLoginForms() {
-    // checks if the page has been flagged for an insecure login. Also checks
-    // if the pref to degrade the UI is set to true
-    return LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser) &&
-           Services.prefs.getBoolPref("security.insecure_password.ui.enabled");
-  },
-
-  // smart getters
-  get _identityPopup() {
-    delete this._identityPopup;
-    return this._identityPopup = document.getElementById("identity-popup");
-  },
-  get _identityBox() {
-    delete this._identityBox;
-    return this._identityBox = document.getElementById("identity-box");
-  },
-  get _identityPopupMultiView() {
-    delete this._identityPopupMultiView;
-    return this._identityPopupMultiView = document.getElementById("identity-popup-multiView");
-  },
-  get _identityPopupMainView() {
-    delete this._identityPopupMainView;
-    return this._identityPopupMainView = document.getElementById("identity-popup-mainView");
-  },
-  get _identityPopupContentHosts() {
-    delete this._identityPopupContentHosts;
-    return this._identityPopupContentHosts =
-      [...document.querySelectorAll(".identity-popup-host")];
-  },
-  get _identityPopupContentHostless() {
-    delete this._identityPopupContentHostless;
-    return this._identityPopupContentHostless =
-      [...document.querySelectorAll(".identity-popup-hostless")];
-  },
-  get _identityPopupContentOwner() {
-    delete this._identityPopupContentOwner;
-    return this._identityPopupContentOwner =
-      document.getElementById("identity-popup-content-owner");
-  },
-  get _identityPopupContentSupp() {
-    delete this._identityPopupContentSupp;
-    return this._identityPopupContentSupp =
-      document.getElementById("identity-popup-content-supplemental");
-  },
-  get _identityPopupContentVerif() {
-    delete this._identityPopupContentVerif;
-    return this._identityPopupContentVerif =
-      document.getElementById("identity-popup-content-verifier");
-  },
-  get _identityPopupMixedContentLearnMore() {
-    delete this._identityPopupMixedContentLearnMore;
-    return this._identityPopupMixedContentLearnMore =
-      document.getElementById("identity-popup-mcb-learn-more");
-  },
-  get _identityPopupInsecureLoginFormsLearnMore() {
-    delete this._identityPopupInsecureLoginFormsLearnMore;
-    return this._identityPopupInsecureLoginFormsLearnMore =
-      document.getElementById("identity-popup-insecure-login-forms-learn-more");
-  },
-  get _identityIconLabels() {
-    delete this._identityIconLabels;
-    return this._identityIconLabels = document.getElementById("identity-icon-labels");
-  },
-  get _identityIconLabel() {
-    delete this._identityIconLabel;
-    return this._identityIconLabel = document.getElementById("identity-icon-label");
-  },
-  get _connectionIcon() {
-    delete this._connectionIcon;
-    return this._connectionIcon = document.getElementById("connection-icon");
-  },
-  get _extensionIcon() {
-    delete this._extensionIcon;
-    return this._extensionIcon = document.getElementById("extension-icon");
-  },
-  get _overrideService() {
-    delete this._overrideService;
-    return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
-                                     .getService(Ci.nsICertOverrideService);
-  },
-  get _identityIconCountryLabel() {
-    delete this._identityIconCountryLabel;
-    return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
-  },
-  get _identityIcon() {
-    delete this._identityIcon;
-    return this._identityIcon = document.getElementById("identity-icon");
-  },
-  get _permissionList() {
-    delete this._permissionList;
-    return this._permissionList = document.getElementById("identity-popup-permission-list");
-  },
-  get _permissionEmptyHint() {
-    delete this._permissionEmptyHint;
-    return this._permissionEmptyHint = document.getElementById("identity-popup-permission-empty-hint");
-  },
-  get _permissionReloadHint() {
-    delete this._permissionReloadHint;
-    return this._permissionReloadHint = document.getElementById("identity-popup-permission-reload-hint");
-  },
-  get _popupExpander() {
-    delete this._popupExpander;
-    return this._popupExpander = document.getElementById("identity-popup-security-expander");
-  },
-  get _permissionAnchors() {
-    delete this._permissionAnchors;
-    let permissionAnchors = {};
-    for (let anchor of document.getElementById("blocked-permissions-container").children) {
-      permissionAnchors[anchor.getAttribute("data-permission-id")] = anchor;
-    }
-    return this._permissionAnchors = permissionAnchors;
-  },
-
-  /**
-   * Handler for mouseclicks on the "More Information" button in the
-   * "identity-popup" panel.
-   */
-  handleMoreInfoClick(event) {
-    displaySecurityInfo();
-    event.stopPropagation();
-    PanelMultiView.hidePopup(this._identityPopup);
-  },
-
-  showSecuritySubView() {
-    this._identityPopupMultiView.showSubView("identity-popup-securityView",
-                                             this._popupExpander);
-
-    // Elements of hidden views have -moz-user-focus:ignore but setting that
-    // per CSS selector doesn't blur a focused element in those hidden views.
-    Services.focus.clearFocus(window);
-  },
-
-  disableMixedContentProtection() {
-    // Use telemetry to measure how often unblocking happens
-    const kMIXED_CONTENT_UNBLOCK_EVENT = 2;
-    let histogram =
-      Services.telemetry.getHistogramById(
-        "MIXED_CONTENT_UNBLOCK_COUNTER");
-    histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT);
-    // Reload the page with the content unblocked
-    BrowserReloadWithFlags(
-      Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
-    PanelMultiView.hidePopup(this._identityPopup);
-  },
-
-  enableMixedContentProtection() {
-    gBrowser.selectedBrowser.messageManager.sendAsyncMessage(
-      "MixedContent:ReenableProtection", {});
-    BrowserReload();
-    PanelMultiView.hidePopup(this._identityPopup);
-  },
-
-  removeCertException() {
-    if (!this._uriHasHost) {
-      Cu.reportError("Trying to revoke a cert exception on a URI without a host?");
-      return;
-    }
-    let host = this._uri.host;
-    let port = this._uri.port > 0 ? this._uri.port : 443;
-    this._overrideService.clearValidityOverride(host, port);
-    BrowserReloadSkipCache();
-    PanelMultiView.hidePopup(this._identityPopup);
-  },
-
-  /**
-   * Helper to parse out the important parts of _sslStatus (of the SSL cert in
-   * particular) for use in constructing identity UI strings
-  */
-  getIdentityData() {
-    var result = {};
-    var cert = this._sslStatus.serverCert;
-
-    // Human readable name of Subject
-    result.subjectOrg = cert.organization;
-
-    // SubjectName fields, broken up for individual access
-    if (cert.subjectName) {
-      result.subjectNameFields = {};
-      cert.subjectName.split(",").forEach(function(v) {
-        var field = v.split("=");
-        this[field[0]] = field[1];
-      }, result.subjectNameFields);
-
-      // Call out city, state, and country specifically
-      result.city = result.subjectNameFields.L;
-      result.state = result.subjectNameFields.ST;
-      result.country = result.subjectNameFields.C;
-    }
-
-    // Human readable name of Certificate Authority
-    result.caOrg =  cert.issuerOrganization || cert.issuerCommonName;
-    result.cert = cert;
-
-    return result;
-  },
-
-  /**
-   * Update the identity user interface for the page currently being displayed.
-   *
-   * This examines the SSL certificate metadata, if available, as well as the
-   * connection type and other security-related state information for the page.
-   *
-   * @param state
-   *        Bitmask provided by nsIWebProgressListener.onSecurityChange.
-   * @param uri
-   *        nsIURI for which the identity UI should be displayed, already
-   *        processed by nsIURIFixup.createExposableURI.
-   */
-  updateIdentity(state, uri) {
-    let shouldHidePopup = this._uri && (this._uri.spec != uri.spec);
-    this._state = state;
-
-    // Firstly, populate the state properties required to display the UI. See
-    // the documentation of the individual properties for details.
-    this.setURI(uri);
-    this._sslStatus = gBrowser.securityUI
-                              .QueryInterface(Ci.nsISSLStatusProvider)
-                              .SSLStatus;
-    if (this._sslStatus) {
-      this._sslStatus.QueryInterface(Ci.nsISSLStatus);
-    }
-
-    // Then, update the user interface with the available data.
-    this.refreshIdentityBlock();
-    // Handle a location change while the Control Center is focused
-    // by closing the popup (bug 1207542)
-    if (shouldHidePopup) {
-      PanelMultiView.hidePopup(this._identityPopup);
-    }
-
-    // NOTE: We do NOT update the identity popup (the control center) when
-    // we receive a new security state on the existing page (i.e. from a
-    // subframe). If the user opened the popup and looks at the provided
-    // information we don't want to suddenly change the panel contents.
-
-    // Finally, if there are warnings to issue, issue them
-    if (this._isCertDistrustImminent) {
-      let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
-      let windowId = gBrowser.selectedBrowser.innerWindowID;
-      let message = gBrowserBundle.GetStringFromName("certImminentDistrust.message");
-      // Use uri.prePath instead of initWithSourceURI() so that these can be
-      // de-duplicated on the scheme+host+port combination.
-      consoleMsg.initWithWindowID(message, uri.prePath, null, 0, 0,
-                                  Ci.nsIScriptError.warningFlag, "SSL",
-                                  windowId);
-      Services.console.logMessage(consoleMsg);
-    }
-  },
-
-  /**
-   * This is called asynchronously when requested by the Logins module, after
-   * the insecure login forms state for the page has been updated.
-   */
-  refreshForInsecureLoginForms() {
-    // Check this._uri because we don't want to refresh the user interface if
-    // this is called before the first page load in the window for any reason.
-    if (!this._uri) {
-      return;
-    }
-    this.refreshIdentityBlock();
-  },
-
-  updateSharingIndicator() {
-    let tab = gBrowser.selectedTab;
-    this._sharingState = tab._sharingState;
-
-    this._identityBox.removeAttribute("paused");
-    this._identityBox.removeAttribute("sharing");
-    if (this._sharingState && this._sharingState.sharing) {
-      this._identityBox.setAttribute("sharing", this._sharingState.sharing);
-      if (this._sharingState.paused) {
-        this._identityBox.setAttribute("paused", "true");
-      }
-    }
-
-    if (this._identityPopup.state == "open") {
-      this.updateSitePermissions();
-      PanelView.forNode(this._identityPopupMainView)
-               .descriptionHeightWorkaround();
-    }
-  },
-
-  /**
-   * Attempt to provide proper IDN treatment for host names
-   */
-  getEffectiveHost() {
-    if (!this._IDNService)
-      this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
-                         .getService(Ci.nsIIDNService);
-    try {
-      return this._IDNService.convertToDisplayIDN(this._uri.host, {});
-    } catch (e) {
-      // If something goes wrong (e.g. host is an IP address) just fail back
-      // to the full domain.
-      return this._uri.host;
-    }
-  },
-
-  /**
-   * Return the CSS class name to set on the "fullscreen-warning" element to
-   * display information about connection security in the notification shown
-   * when a site enters the fullscreen mode.
-   */
-  get pointerlockFsWarningClassName() {
-    // Note that the fullscreen warning does not handle _isSecureInternalUI.
-    if (this._uriHasHost && this._isEV) {
-      return "verifiedIdentity";
-    }
-    if (this._uriHasHost && this._isSecure) {
-      return "verifiedDomain";
-    }
-    return "unknownIdentity";
-  },
-
-  /**
-   * Updates the identity block user interface with the data from this object.
-   */
-  refreshIdentityBlock() {
-    if (!this._identityBox) {
-      return;
-    }
-
-    let icon_label = "";
-    let tooltip = "";
-    let icon_country_label = "";
-    let icon_labels_dir = "ltr";
-
-    if (this._isSecureInternalUI) {
-      this._identityBox.className = "chromeUI";
-      let brandBundle = document.getElementById("bundle_brand");
-      icon_label = brandBundle.getString("brandShorterName");
-    } else if (this._uriHasHost && this._isEV) {
-      this._identityBox.className = "verifiedIdentity";
-      if (this._isMixedActiveContentBlocked) {
-        this._identityBox.classList.add("mixedActiveBlocked");
-      }
-
-      if (!this._isCertUserOverridden) {
-        // If it's identified, then we can populate the dialog with credentials
-        let iData = this.getIdentityData();
-        tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
-                                                      [iData.caOrg]);
-        icon_label = iData.subjectOrg;
-        if (iData.country)
-          icon_country_label = "(" + iData.country + ")";
-
-        // If the organization name starts with an RTL character, then
-        // swap the positions of the organization and country code labels.
-        // The Unicode ranges reflect the definition of the UTF16_CODE_UNIT_IS_BIDI
-        // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
-        // fixed, this test should be replaced by one adhering to the
-        // Unicode Bidirectional Algorithm proper (at the paragraph level).
-        icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc\ud802\ud803\ud83a\ud83b]/.test(icon_label) ?
-                          "rtl" : "ltr";
-      }
-    } else if (this._pageExtensionPolicy) {
-      this._identityBox.className = "extensionPage";
-      let extensionName = this._pageExtensionPolicy.name;
-      icon_label = gNavigatorBundle.getFormattedString(
-        "identity.extension.label", [extensionName]);
-    } else if (this._uriHasHost && this._isSecure) {
-      this._identityBox.className = "verifiedDomain";
-      if (this._isMixedActiveContentBlocked) {
-        this._identityBox.classList.add("mixedActiveBlocked");
-      }
-      if (!this._isCertUserOverridden) {
-        // It's a normal cert, verifier is the CA Org.
-        tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
-                                                      [this.getIdentityData().caOrg]);
-      }
-    } else if (!this._uriHasHost) {
-      this._identityBox.className = "unknownIdentity";
-    } else if (gBrowser.selectedBrowser.documentURI &&
-               (gBrowser.selectedBrowser.documentURI.scheme == "about" ||
-               gBrowser.selectedBrowser.documentURI.scheme == "chrome")) {
-        // For net errors we should not show notSecure as it's likely confusing
-      this._identityBox.className = "unknownIdentity";
-    } else {
-      if (this._isBroken) {
-        this._identityBox.className = "unknownIdentity";
-
-        if (this._isMixedActiveContentLoaded) {
-          this._identityBox.classList.add("mixedActiveContent");
-        } else if (this._isMixedActiveContentBlocked) {
-          this._identityBox.classList.add("mixedDisplayContentLoadedActiveBlocked");
-        } else if (this._isMixedPassiveContentLoaded) {
-          this._identityBox.classList.add("mixedDisplayContent");
-        } else {
-          this._identityBox.classList.add("weakCipher");
-        }
-      } else {
-        let warnOnInsecure = Services.prefs.getBoolPref("security.insecure_connection_icon.enabled") ||
-                             (Services.prefs.getBoolPref("security.insecure_connection_icon.pbmode.enabled") &&
-                             PrivateBrowsingUtils.isWindowPrivate(window));
-        let className = warnOnInsecure ? "notSecure" : "unknownIdentity";
-        this._identityBox.className = className;
-
-        let warnTextOnInsecure = Services.prefs.getBoolPref("security.insecure_connection_text.enabled") ||
-                                 (Services.prefs.getBoolPref("security.insecure_connection_text.pbmode.enabled") &&
-                                 PrivateBrowsingUtils.isWindowPrivate(window));
-        if (warnTextOnInsecure) {
-          icon_label = gNavigatorBundle.getString("identity.notSecure.label");
-          this._identityBox.classList.add("notSecureText");
-        }
-      }
-      if (this._hasInsecureLoginForms) {
-        // Insecure login forms can only be present on "unknown identity"
-        // pages, either already insecure or with mixed active content loaded.
-        this._identityBox.classList.add("insecureLoginForms");
-      }
-    }
-
-    if (this._isCertUserOverridden) {
-      this._identityBox.classList.add("certUserOverridden");
-      // Cert is trusted because of a security exception, verifier is a special string.
-      tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
-    }
-
-    let permissionAnchors = this._permissionAnchors;
-
-    // hide all permission icons
-    for (let icon of Object.values(permissionAnchors)) {
-      icon.removeAttribute("showing");
-    }
-
-    // keeps track if we should show an indicator that there are active permissions
-    let hasGrantedPermissions = false;
-
-    // show permission icons
-    let permissions = SitePermissions.getAllForBrowser(gBrowser.selectedBrowser);
-    for (let permission of permissions) {
-      if (permission.state == SitePermissions.BLOCK) {
-
-        let icon = permissionAnchors[permission.id];
-        if (icon) {
-          icon.setAttribute("showing", "true");
-        }
-
-      } else if (permission.state != SitePermissions.UNKNOWN) {
-        hasGrantedPermissions = true;
-      }
-    }
-
-    if (hasGrantedPermissions) {
-      this._identityBox.classList.add("grantedPermissions");
-    }
-
-    // Show blocked popup icon in the identity-box if popups are blocked
-    // irrespective of popup permission capability value.
-    if (gBrowser.selectedBrowser.blockedPopups &&
-        gBrowser.selectedBrowser.blockedPopups.length) {
-      let icon = permissionAnchors.popup;
-      icon.setAttribute("showing", "true");
-    }
-
-    // Push the appropriate strings out to the UI
-    this._connectionIcon.setAttribute("tooltiptext", tooltip);
-
-    if (this._pageExtensionPolicy) {
-      let extensionName = this._pageExtensionPolicy.name;
-      this._extensionIcon.setAttribute("tooltiptext",
-        gNavigatorBundle.getFormattedString("identity.extension.tooltip", [extensionName]));
-    }
-
-    this._identityIconLabels.setAttribute("tooltiptext", tooltip);
-    this._identityIcon.setAttribute("tooltiptext", gNavigatorBundle.getString("identity.icon.tooltip"));
-    this._identityIconLabel.setAttribute("value", icon_label);
-    this._identityIconCountryLabel.setAttribute("value", icon_country_label);
-    // Set cropping and direction
-    this._identityIconLabel.setAttribute("crop", icon_country_label ? "end" : "center");
-    this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
-    // Hide completely if the organization label is empty
-    this._identityIconLabel.parentNode.collapsed = !icon_label;
-  },
-
-  /**
-   * Set up the title and content messages for the identity message popup,
-   * based on the specified mode, and the details of the SSL cert, where
-   * applicable
-   */
-  refreshIdentityPopup() {
-    // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
-    let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
-    this._identityPopupMixedContentLearnMore
-        .setAttribute("href", baseURL + "mixed-content");
-    this._identityPopupInsecureLoginFormsLearnMore
-        .setAttribute("href", baseURL + "insecure-password");
-
-    // This is in the properties file because the expander used to switch its tooltip.
-    this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.showDetails.tooltip");
-
-    // Determine connection security information.
-    let connection = "not-secure";
-    if (this._isSecureInternalUI) {
-      connection = "chrome";
-    } else if (this._pageExtensionPolicy) {
-      connection = "extension";
-    } else if (this._isURILoadedFromFile) {
-      connection = "file";
-    } else if (this._isEV) {
-      connection = "secure-ev";
-    } else if (this._isCertUserOverridden) {
-      connection = "secure-cert-user-overridden";
-    } else if (this._isSecure) {
-      connection = "secure";
-    }
-
-    // Determine if there are insecure login forms.
-    let loginforms = "secure";
-    if (this._hasInsecureLoginForms) {
-      loginforms = "insecure";
-    }
-
-    // Determine the mixed content state.
-    let mixedcontent = [];
-    if (this._isMixedPassiveContentLoaded) {
-      mixedcontent.push("passive-loaded");
-    }
-    if (this._isMixedActiveContentLoaded) {
-      mixedcontent.push("active-loaded");
-    } else if (this._isMixedActiveContentBlocked) {
-      mixedcontent.push("active-blocked");
-    }
-    mixedcontent = mixedcontent.join(" ");
-
-    // We have no specific flags for weak ciphers (yet). If a connection is
-    // broken and we can't detect any mixed content loaded then it's a weak
-    // cipher.
-    let ciphers = "";
-    if (this._isBroken && !this._isMixedActiveContentLoaded && !this._isMixedPassiveContentLoaded) {
-      ciphers = "weak";
-    }
-
-    // Update all elements.
-    let elementIDs = [
-      "identity-popup",
-      "identity-popup-securityView-body",
-    ];
-
-    function updateAttribute(elem, attr, value) {
-      if (value) {
-        elem.setAttribute(attr, value);
-      } else {
-        elem.removeAttribute(attr);
-      }
-    }
-
-    for (let id of elementIDs) {
-      let element = document.getElementById(id);
-      updateAttribute(element, "connection", connection);
-      updateAttribute(element, "loginforms", loginforms);
-      updateAttribute(element, "ciphers", ciphers);
-      updateAttribute(element, "mixedcontent", mixedcontent);
-      updateAttribute(element, "isbroken", this._isBroken);
-    }
-
-    // Initialize the optional strings to empty values
-    let supplemental = "";
-    let verifier = "";
-    let host = "";
-    let owner = "";
-    let hostless = false;
-
-    try {
-      host = this.getEffectiveHost();
-    } catch (e) {
-      // Some URIs might have no hosts.
-    }
-
-    // Fallback for special protocols.
-    if (!host) {
-      host = this._uri.specIgnoringRef;
-      // Special URIs without a host (eg, about:) should crop the end so
-      // the protocol can be seen.
-      hostless = true;
-    }
-
-    if (this._pageExtensionPolicy) {
-      host = this._pageExtensionPolicy.name;
-    }
-
-    // Fill in the CA name if we have a valid TLS certificate.
-    if (this._isSecure || this._isCertUserOverridden) {
-      verifier = this._identityIconLabels.tooltipText;
-    }
-
-    // Fill in organization information if we have a valid EV certificate.
-    if (this._isEV) {
-      let iData = this.getIdentityData();
-      host = owner = iData.subjectOrg;
-      verifier = this._identityIconLabels.tooltipText;
-
-      // Build an appropriate supplemental block out of whatever location data we have
-      if (iData.city)
-        supplemental += iData.city + "\n";
-      if (iData.state && iData.country)
-        supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country",
-                                                            [iData.state, iData.country]);
-      else if (iData.state) // State only
-        supplemental += iData.state;
-      else if (iData.country) // Country only
-        supplemental += iData.country;
-    }
-
-    // Push the appropriate strings out to the UI.
-    this._identityPopupContentHosts.forEach((el) => {
-      el.textContent = host;
-      el.hidden = hostless;
-    });
-    this._identityPopupContentHostless.forEach((el) => {
-      el.setAttribute("value", host);
-      el.hidden = !hostless;
-    });
-    this._identityPopupContentOwner.textContent = owner;
-    this._identityPopupContentSupp.textContent = supplemental;
-    this._identityPopupContentVerif.textContent = verifier;
-
-    // Update per-site permissions section.
-    this.updateSitePermissions();
-  },
-
-  setURI(uri) {
-    this._uri = uri;
-
-    try {
-      // Account for file: urls and catch when "" is the value
-      this._uriHasHost = !!this._uri.host;
-    } catch (ex) {
-      this._uriHasHost = false;
-    }
-
-    this._isSecureInternalUI = uri.schemeIs("about") &&
-      this._secureInternalUIWhitelist.test(uri.pathQueryRef);
-
-    this._pageExtensionPolicy = WebExtensionPolicy.getByURI(uri);
-
-    // Create a channel for the sole purpose of getting the resolved URI
-    // of the request to determine if it's loaded from the file system.
-    this._isURILoadedFromFile = false;
-    let chanOptions = {uri: this._uri, loadUsingSystemPrincipal: true};
-    let resolvedURI;
-    try {
-      resolvedURI = NetUtil.newChannel(chanOptions).URI;
-      if (resolvedURI.schemeIs("jar")) {
-        // Given a URI "jar:<jar-file-uri>!/<jar-entry>"
-        // create a new URI using <jar-file-uri>!/<jar-entry>
-        resolvedURI = NetUtil.newURI(resolvedURI.pathQueryRef);
-      }
-      // Check the URI again after resolving.
-      this._isURILoadedFromFile = resolvedURI.schemeIs("file");
-    } catch (ex) {
-      // NetUtil's methods will throw for malformed URIs and the like
-    }
-  },
-
-  /**
-   * Click handler for the identity-box element in primary chrome.
-   */
-  handleIdentityButtonEvent(event) {
-    event.stopPropagation();
-
-    if ((event.type == "click" && event.button != 0) ||
-        (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
-         event.keyCode != KeyEvent.DOM_VK_RETURN)) {
-      return; // Left click, space or enter only
-    }
-
-    // Don't allow left click, space or enter if the location has been modified,
-    // so long as we're not sharing any devices.
-    // If we are sharing a device, the identity block is prevented by CSS from
-    // being focused (and therefore, interacted with) by the user. However, we
-    // want to allow opening the identity popup from the device control menu,
-    // which calls click() on the identity button, so we don't return early.
-    if (!this._sharingState &&
-        gURLBar.getAttribute("pageproxystate") != "valid") {
-      return;
-    }
-
-    this._popupTriggeredByKeyboard = event.type == "keypress";
-
-    // Make sure that the display:none style we set in xul is removed now that
-    // the popup is actually needed
-    this._identityPopup.hidden = false;
-
-    // Remove the reload hint that we show after a user has cleared a permission.
-    this._permissionReloadHint.setAttribute("hidden", "true");
-
-    // Update the popup strings
-    this.refreshIdentityPopup();
-
-    // Add the "open" attribute to the identity box for styling
-    this._identityBox.setAttribute("open", "true");
-
-    // Now open the popup, anchored off the primary chrome element
-    PanelMultiView.openPopup(this._identityPopup, this._identityIcon,
-                             "bottomcenter topleft").catch(Cu.reportError);
-  },
-
-  onPopupShown(event) {
-    if (event.target == this._identityPopup) {
-      if (this._popupTriggeredByKeyboard) {
-        // Move focus to the next available element in the identity popup.
-        // This is required by role=alertdialog and fixes an issue where
-        // an already open panel would steal focus from the identity popup.
-        document.commandDispatcher.advanceFocusIntoSubtree(this._identityPopup);
-      }
-
-      window.addEventListener("focus", this, true);
-    }
-  },
-
-  onPopupHidden(event) {
-    if (event.target == this._identityPopup) {
-      window.removeEventListener("focus", this, true);
-      this._identityBox.removeAttribute("open");
-    }
-  },
-
-  handleEvent(event) {
-    let elem = document.activeElement;
-    let position = elem.compareDocumentPosition(this._identityPopup);
-
-    if (!(position & (Node.DOCUMENT_POSITION_CONTAINS |
-                      Node.DOCUMENT_POSITION_CONTAINED_BY)) &&
-        !this._identityPopup.hasAttribute("noautohide")) {
-      // Hide the panel when focusing an element that is
-      // neither an ancestor nor descendant unless the panel has
-      // @noautohide (e.g. for a tour).
-      PanelMultiView.hidePopup(this._identityPopup);
-    }
-  },
-
-  observe(subject, topic, data) {
-    if (topic == "perm-changed") {
-      this.refreshIdentityBlock();
-    }
-  },
-
-  onDragStart(event) {
-    if (gURLBar.getAttribute("pageproxystate") != "valid")
-      return;
-
-    let value = gBrowser.currentURI.displaySpec;
-    let urlString = value + "\n" + gBrowser.contentTitle;
-    let htmlString = "<a href=\"" + value + "\">" + value + "</a>";
-
-    let dt = event.dataTransfer;
-    dt.setData("text/x-moz-url", urlString);
-    dt.setData("text/uri-list", value);
-    dt.setData("text/plain", value);
-    dt.setData("text/html", htmlString);
-    dt.setDragImage(this._identityIcon, 16, 16);
-  },
-
-  onLocationChange() {
-    this._permissionReloadHint.setAttribute("hidden", "true");
-
-    if (!this._permissionList.hasChildNodes()) {
-      this._permissionEmptyHint.removeAttribute("hidden");
-    }
-  },
-
-  updateSitePermissions() {
-    while (this._permissionList.hasChildNodes())
-      this._permissionList.removeChild(this._permissionList.lastChild);
-
-    let permissions =
-      SitePermissions.getAllPermissionDetailsForBrowser(gBrowser.selectedBrowser);
-
-    if (this._sharingState) {
-      // If WebRTC device or screen permissions are in use, we need to find
-      // the associated permission item to set the sharingState field.
-      for (let id of ["camera", "microphone", "screen"]) {
-        if (this._sharingState[id]) {
-          let found = false;
-          for (let permission of permissions) {
-            if (permission.id != id)
-              continue;
-            found = true;
-            permission.sharingState = this._sharingState[id];
-            break;
-          }
-          if (!found) {
-            // If the permission item we were looking for doesn't exist,
-            // the user has temporarily allowed sharing and we need to add
-            // an item in the permissions array to reflect this.
-            permissions.push({
-              id,
-              state: SitePermissions.ALLOW,
-              scope: SitePermissions.SCOPE_REQUEST,
-              sharingState: this._sharingState[id],
-            });
-          }
-        }
-      }
-    }
-
-    let hasBlockedPopupIndicator = false;
-    for (let permission of permissions) {
-      let item = this._createPermissionItem(permission);
-      this._permissionList.appendChild(item);
-
-      if (permission.id == "popup" &&
-          gBrowser.selectedBrowser.blockedPopups &&
-          gBrowser.selectedBrowser.blockedPopups.length) {
-        this._createBlockedPopupIndicator();
-        hasBlockedPopupIndicator = true;
-      }
-    }
-
-    if (gBrowser.selectedBrowser.blockedPopups &&
-        gBrowser.selectedBrowser.blockedPopups.length &&
-        !hasBlockedPopupIndicator) {
-      let permission = {
-        id: "popup",
-        state: SitePermissions.getDefault("popup"),
-        scope: SitePermissions.SCOPE_PERSISTENT,
-      };
-      let item = this._createPermissionItem(permission);
-      this._permissionList.appendChild(item);
-      this._createBlockedPopupIndicator();
-    }
-
-    // Show a placeholder text if there's no permission and no reload hint.
-    if (!this._permissionList.hasChildNodes() &&
-        this._permissionReloadHint.hasAttribute("hidden")) {
-      this._permissionEmptyHint.removeAttribute("hidden");
-    } else {
-      this._permissionEmptyHint.setAttribute("hidden", "true");
-    }
-  },
-
-  _createPermissionItem(aPermission) {
-    let container = document.createElement("hbox");
-    container.setAttribute("class", "identity-popup-permission-item");
-    container.setAttribute("align", "center");
-
-    let img = document.createElement("image");
-    img.classList.add("identity-popup-permission-icon");
-    if (aPermission.id == "plugin:flash") {
-      img.classList.add("plugin-icon");
-    } else {
-      img.classList.add(aPermission.id + "-icon");
-    }
-    if (aPermission.state == SitePermissions.BLOCK)
-      img.classList.add("blocked-permission-icon");
-
-    if (aPermission.sharingState == Ci.nsIMediaManagerService.STATE_CAPTURE_ENABLED ||
-       (aPermission.id == "screen" && aPermission.sharingState &&
-        !aPermission.sharingState.includes("Paused"))) {
-      img.classList.add("in-use");
-
-      // Synchronize control center and identity block blinking animations.
-      window.promiseDocumentFlushed(() => {
-        let sharingIconBlink = document.getElementById("sharing-icon").getAnimations()[0];
-        let imgBlink = img.getAnimations()[0];
-        return [sharingIconBlink, imgBlink];
-      }).then(([sharingIconBlink, imgBlink]) => {
-        if (sharingIconBlink && imgBlink) {
-          imgBlink.startTime = sharingIconBlink.startTime;
-        }
-      });
-    }
-
-    let nameLabel = document.createElement("label");
-    nameLabel.setAttribute("flex", "1");
-    nameLabel.setAttribute("class", "identity-popup-permission-label");
-    nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
-
-    let isPolicyPermission = aPermission.scope == SitePermissions.SCOPE_POLICY;
-
-    if (aPermission.id == "popup" && !isPolicyPermission) {
-      let menulist = document.createElement("menulist");
-      let menupopup = document.createElement("menupopup");
-      let block = document.createElement("vbox");
-      block.setAttribute("id", "identity-popup-popup-container");
-      menulist.setAttribute("sizetopopup", "none");
-      menulist.setAttribute("class", "identity-popup-popup-menulist");
-      menulist.setAttribute("id", "identity-popup-popup-menulist");
-
-      for (let state of SitePermissions.getAvailableStates(aPermission.id)) {
-        let menuitem = document.createElement("menuitem");
-        // We need to correctly display the default/unknown state, which has its
-        // own integer value (0) but represents one of the other states.
-        if (state == SitePermissions.getDefault(aPermission.id)) {
-          menuitem.setAttribute("value", "0");
-        } else {
-          menuitem.setAttribute("value", state);
-        }
-        menuitem.setAttribute("label", SitePermissions.getMultichoiceStateLabel(state));
-        menupopup.appendChild(menuitem);
-      }
-
-      menulist.appendChild(menupopup);
-
-      if (aPermission.state == SitePermissions.getDefault(aPermission.id)) {
-        menulist.value = "0";
-      } else {
-        menulist.value = aPermission.state;
-      }
-
-      // Avoiding listening to the "select" event on purpose. See Bug 1404262.
-      menulist.addEventListener("command", () => {
-        SitePermissions.set(gBrowser.currentURI, "popup", menulist.selectedItem.value);
-      });
-
-      container.appendChild(img);
-      container.appendChild(nameLabel);
-      container.appendChild(menulist);
-      block.appendChild(container);
-
-      return block;
-    }
-
-    let stateLabel = document.createElement("label");
-    stateLabel.setAttribute("flex", "1");
-    stateLabel.setAttribute("class", "identity-popup-permission-state-label");
-    let {state, scope} = aPermission;
-    // If the user did not permanently allow this device but it is currently
-    // used, set the variables to display a "temporarily allowed" info.
-    if (state != SitePermissions.ALLOW && aPermission.sharingState) {
-      state = SitePermissions.ALLOW;
-      scope = SitePermissions.SCOPE_REQUEST;
-    }
-    stateLabel.textContent = SitePermissions.getCurrentStateLabel(state, aPermission.id, scope);
-
-    container.appendChild(img);
-    container.appendChild(nameLabel);
-    container.appendChild(stateLabel);
-
-    /* We return the permission item here without a remove button if the permission is a
-       SCOPE_POLICY permission. Policy permissions cannot be removed/changed for the duration
-       of the browser session. */
-    if (isPolicyPermission) {
-      return container;
-    }
-
-    let button = document.createElement("button");
-    button.setAttribute("class", "identity-popup-permission-remove-button");
-    let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
-    button.setAttribute("tooltiptext", tooltiptext);
-    button.addEventListener("command", () => {
-      let browser = gBrowser.selectedBrowser;
-      this._permissionList.removeChild(container);
-      if (aPermission.sharingState &&
-          ["camera", "microphone", "screen"].includes(aPermission.id)) {
-        let windowId = this._sharingState.windowId;
-        if (aPermission.id == "screen") {
-          windowId = "screen:" + windowId;
-        } else {
-          // If we set persistent permissions or the sharing has
-          // started due to existing persistent permissions, we need
-          // to handle removing these even for frames with different hostnames.
-          let uris = browser._devicePermissionURIs || [];
-          for (let uri of uris) {
-            // It's not possible to stop sharing one of camera/microphone
-            // without the other.
-            for (let id of ["camera", "microphone"]) {
-              if (this._sharingState[id]) {
-                let perm = SitePermissions.get(uri, id);
-                if (perm.state == SitePermissions.ALLOW &&
-                    perm.scope == SitePermissions.SCOPE_PERSISTENT) {
-                  SitePermissions.remove(uri, id);
-                }
-              }
-            }
-          }
-        }
-        browser.messageManager.sendAsyncMessage("webrtc:StopSharing", windowId);
-        webrtcUI.forgetActivePermissionsFromBrowser(gBrowser.selectedBrowser);
-      }
-      SitePermissions.remove(gBrowser.currentURI, aPermission.id, browser);
-
-      this._permissionReloadHint.removeAttribute("hidden");
-      PanelView.forNode(this._identityPopupMainView)
-               .descriptionHeightWorkaround();
-    });
-
-    container.appendChild(button);
-
-    return container;
-  },
-
-  _createBlockedPopupIndicator() {
-    let indicator = document.createElement("hbox");
-    indicator.setAttribute("class", "identity-popup-permission-item");
-    indicator.setAttribute("align", "center");
-    indicator.setAttribute("id", "blocked-popup-indicator-item");
-
-    let icon = document.createElement("image");
-    icon.setAttribute("class", "popup-subitem identity-popup-permission-icon");
-
-    let text = document.createElement("label");
-    text.setAttribute("flex", "1");
-    text.setAttribute("class", "identity-popup-permission-label text-link");
-
-    let popupCount = gBrowser.selectedBrowser.blockedPopups.length;
-    let messageBase = gNavigatorBundle.getString("popupShowBlockedPopupsIndicatorText");
-    let message = PluralForm.get(popupCount, messageBase)
-                                 .replace("#1", popupCount);
-    text.textContent = message;
-
-    text.addEventListener("click", () => {
-      gPopupBlockerObserver.showAllBlockedPopups(gBrowser.selectedBrowser);
-    });
-
-    indicator.appendChild(icon);
-    indicator.appendChild(text);
-
-    document.getElementById("identity-popup-popup-container").appendChild(indicator);
-  },
-};
-
-/**
  * Fired on the "marionette-remote-control" system notification,
  * indicating if the browser session is under remote control.
  */
 const gRemoteControl = {
   observe(subject, topic, data) {
     gRemoteControl.updateVisualCue(data);
   },
 
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/contentSearchUI.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 this.ContentSearchUIController = (function() {
 
 const MAX_DISPLAYED_SUGGESTIONS = 6;
 const SUGGESTION_ID_PREFIX = "searchSuggestion";
 const ONE_OFF_ID_PREFIX = "oneOff";
+const DEFAULT_INPUT_ICON = "chrome://browser/skin/search-glass.svg";
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
  * Creates a new object that manages search suggestions and their UI for a text
  * box.
  *
  * The UI consists of an html:table that's inserted into the DOM after the given
@@ -86,16 +87,17 @@ ContentSearchUIController.prototype = {
     } else {
       icon = "chrome://mozapps/skin/places/defaultFavicon.svg";
     }
     this._defaultEngine = {
       name: engine.name,
       icon,
     };
     this._updateDefaultEngineHeader();
+    this._updateDefaultEngineIcon();
 
     if (engine && document.activeElement == this.input) {
       this._speculativeConnect();
     }
   },
 
   get engines() {
     return this._engines;
@@ -604,16 +606,24 @@ ContentSearchUIController.prototype = {
   _onMsgStrings(strings) {
     this._strings = strings;
     this._updateDefaultEngineHeader();
     this._updateSearchWithHeader();
     document.getElementById("contentSearchSettingsButton").textContent =
       this._strings.searchSettings;
   },
 
+  _updateDefaultEngineIcon() {
+    let eng = this._engines.find(engine => engine.name === this.defaultEngine.name);
+    // We only show the engines icon for default engines, otherwise show
+    // a default; default engines have an identifier
+    let icon = eng.identifier ? this.defaultEngine.icon : DEFAULT_INPUT_ICON;
+    document.body.style.setProperty("--newtab-search-icon", "url(" + icon + ")");
+  },
+
   _updateDefaultEngineHeader() {
     let header = document.getElementById("contentSearchDefaultEngineHeader");
     header.firstChild.setAttribute("src", this.defaultEngine.icon);
     if (!this._strings) {
       return;
     }
     while (header.firstChild.nextSibling) {
       header.firstChild.nextSibling.remove();
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -881,17 +881,17 @@ nsContextMenu.prototype = {
     let openSelectionFn = function() {
       let tabBrowser = gBrowser;
       // In the case of popups, we need to find a non-popup browser window.
       // We might also not have a tabBrowser reference (if this isn't in a
       // a tabbrowser scope) or might have a fake/stub tabbrowser reference
       // (in the sidebar). Deal with those cases:
       if (!tabBrowser || !tabBrowser.loadOneTab || !window.toolbar.visible) {
         // This returns only non-popup browser windows by default.
-        let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+        let browserWindow = BrowserWindowTracker.getTopWindow();
         tabBrowser = browserWindow.gBrowser;
       }
       let relatedToCurrent = gBrowser && gBrowser.selectedBrowser == browser;
       let tab = tabBrowser.loadOneTab("about:blank", {
         relatedToCurrent,
         inBackground: false,
         triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
       });
--- a/browser/base/content/test/captivePortal/head.js
+++ b/browser/base/content/test/captivePortal/head.js
@@ -1,9 +1,9 @@
-ChromeUtils.import("resource:///modules/RecentWindow.jsm");
+ChromeUtils.import("resource:///modules/BrowserWindowTracker.jsm");
 
 ChromeUtils.defineModuleGetter(this, "CaptivePortalWatcher",
   "resource:///modules/CaptivePortalWatcher.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cps",
                                    "@mozilla.org/network/captive-portal-service;1",
                                    "nsICaptivePortalService");
 
@@ -15,31 +15,31 @@ const PORTAL_NOTIFICATION_VALUE = "capti
 async function setupPrefsAndRecentWindowBehavior() {
   await SpecialPowers.pushPrefEnv({
     set: [["captivedetect.canonicalURL", CANONICAL_URL],
           ["captivedetect.canonicalContent", CANONICAL_CONTENT]],
   });
   // We need to test behavior when a portal is detected when there is no browser
   // window, but we can't close the default window opened by the test harness.
   // Instead, we deactivate CaptivePortalWatcher in the default window and
-  // exclude it from RecentWindow.getMostRecentBrowserWindow in an attempt to
+  // exclude it from BrowserWindowTracker.getTopWindow in an attempt to
   // mask its presence.
   window.CaptivePortalWatcher.uninit();
-  let getMostRecentBrowserWindowCopy = RecentWindow.getMostRecentBrowserWindow;
+  let getTopWindowCopy = BrowserWindowTracker.getTopWindow;
   let defaultWindow = window;
-  RecentWindow.getMostRecentBrowserWindow = () => {
-    let win = getMostRecentBrowserWindowCopy();
+  BrowserWindowTracker.getTopWindow = () => {
+    let win = getTopWindowCopy();
     if (win == defaultWindow) {
       return null;
     }
     return win;
   };
 
   registerCleanupFunction(function cleanUp() {
-    RecentWindow.getMostRecentBrowserWindow = getMostRecentBrowserWindowCopy;
+    BrowserWindowTracker.getTopWindow = getTopWindowCopy;
     window.CaptivePortalWatcher.init();
   });
 }
 
 async function portalDetected() {
   Services.obs.notifyObservers(null, "captive-portal-login");
   await BrowserTestUtils.waitForCondition(() => {
     return cps.state == cps.LOCKED_PORTAL;
--- a/browser/base/content/test/general/browser_offlineQuotaNotification.js
+++ b/browser/base/content/test/general/browser_offlineQuotaNotification.js
@@ -10,17 +10,17 @@ const URL = "http://mochi.test:8888/brow
 
 registerCleanupFunction(function() {
   // Clean up after ourself
   let uri = Services.io.newURI(URL);
   let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
   Services.perms.removeFromPrincipal(principal, "offline-app");
   Services.prefs.clearUserPref("offline-apps.quota.warn");
   Services.prefs.clearUserPref("offline-apps.allow_by_default");
-  let {OfflineAppCacheHelper} = ChromeUtils.import("resource:///modules/offlineAppCache.jsm", {});
+  let {OfflineAppCacheHelper} = ChromeUtils.import("resource://gre/modules/offlineAppCache.jsm", {});
   OfflineAppCacheHelper.clear();
 });
 
 // Same as the other one, but for in-content preferences
 function checkInContentPreferences(win) {
   let doc = win.document;
   let sel = doc.getElementById("categories").selectedItems[0].id;
   is(gBrowser.currentURI.spec, "about:preferences#privacy", "about:preferences loaded");
--- a/browser/base/content/test/performance/browser_startup.js
+++ b/browser/base/content/test/performance/browser_startup.js
@@ -85,18 +85,18 @@ const startupPhases = {
   // interacting with the first browser window.
   "before handling user events": {blacklist: {
     components: new Set([
       "PageIconProtocolHandler.js",
       "PlacesCategoriesStarter.js",
       "nsPlacesExpiration.js",
     ]),
     modules: new Set([
-      // Bug 1391495 - RecentWindow.jsm is intermittently used.
-      // "resource:///modules/RecentWindow.jsm",
+      // Bug 1391495 - BrowserWindowTracker.jsm is intermittently used.
+      // "resource:///modules/BrowserWindowTracker.jsm",
       "resource://gre/modules/BookmarkHTMLUtils.jsm",
       "resource://gre/modules/Bookmarks.jsm",
       "resource://gre/modules/ContextualIdentityService.jsm",
       "resource://gre/modules/CrashSubmit.jsm",
       "resource://gre/modules/FxAccounts.jsm",
       "resource://gre/modules/FxAccountsStorage.jsm",
       "resource://gre/modules/PlacesBackups.jsm",
       "resource://gre/modules/PlacesSyncUtils.jsm",
@@ -123,17 +123,17 @@ const startupPhases = {
   }},
 };
 
 if (Services.prefs.getBoolPref("browser.startup.blankWindow")) {
   startupPhases["before profile selection"].whitelist.components.add("XULStore.js");
 }
 
 if (!gBrowser.selectedBrowser.isRemoteBrowser) {
-  // With e10s disabled, Places and RecentWindow.jsm (from a
+  // With e10s disabled, Places and BrowserWindowTracker.jsm (from a
   // SessionSaver.jsm timer) intermittently get loaded earlier. Likely
   // due to messages from the 'content' process arriving synchronously
   // instead of crossing a process boundary.
   info("merging the 'before handling user events' blacklist into the " +
        "'before first paint' one when e10s is disabled.");
   let from = startupPhases["before handling user events"].blacklist;
   let to = startupPhases["before first paint"].blacklist;
   for (let scriptType in from) {
--- a/browser/base/content/test/urlbar/browser_page_action_menu_share_mac.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu_share_mac.js
@@ -2,16 +2,18 @@
  * 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";
 
 /* global sinon */
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
 
+const URL = "http://example.org/";
+
 // Keep track of title of service we chose to share with
 let sharedTitle;
 let sharedUrl;
 
 let mockShareData = [{
   title: "NSA",
   menuItemTitle: "National Security Agency",
   image: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEA" +
@@ -33,21 +35,17 @@ let stub = sinon.stub(BrowserPageActions
 registerCleanupFunction(async function() {
   stub.restore();
   delete window.sinon;
   await EventUtils.synthesizeNativeMouseMove(document.documentElement, 0, 0);
   await PlacesUtils.history.clear();
 });
 
 add_task(async function shareURL() {
-  // Open an actionable page so that the main page action button appears.  (It
-  // does not appear on about:blank for example.)
-  let url = "http://example.org/";
-
-  await BrowserTestUtils.withNewTab(url, async () => {
+  await BrowserTestUtils.withNewTab(URL, async () => {
     // Open the panel.
     await promisePageActionPanelOpen();
 
     // Click Share URL.
     let shareURLButton = document.getElementById("pageAction-panel-shareURL");
     let viewPromise = promisePageActionViewShown();
     EventUtils.synthesizeMouseAtCenter(shareURLButton, {});
 
@@ -62,11 +60,64 @@ add_task(async function shareURL() {
     // given the title of service to share with
     EventUtils.synthesizeMouseAtCenter(shareButton, {});
     await hiddenPromise;
 
     Assert.equal(sharedTitle, mockShareData[0].title,
                  "Shared with the correct title");
     Assert.equal(sharedUrl, "http://example.org/",
                  "Shared correct URL");
-
   });
 });
+
+add_task(async function shareURLAddressBar() {
+  await BrowserTestUtils.withNewTab(URL, async () => {
+    // Open pageAction panel
+    await promisePageActionPanelOpen();
+
+    // Right click the Share button
+    let contextMenuPromise = promisePanelShown("pageActionContextMenu");
+    let shareURLButton = document.getElementById("pageAction-panel-shareURL");
+    EventUtils.synthesizeMouseAtCenter(shareURLButton, {
+      type: "contextmenu",
+      button: 2,
+    });
+    await contextMenuPromise;
+
+    // Click "Add to Address Bar"
+    contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+    let ctxMenuButton = document.querySelector("#pageActionContextMenu " +
+                                               ".pageActionContextMenuItem");
+    EventUtils.synthesizeMouseAtCenter(ctxMenuButton, {});
+    await contextMenuPromise;
+
+    // Wait for the Share button to be added
+    await BrowserTestUtils.waitForCondition(() => {
+      return document.getElementById("pageAction-urlbar-shareURL");
+    }, "Waiting for the share url button to be added to url bar");
+
+
+    // Press the Share button
+    let shareButton = document.getElementById("pageAction-urlbar-shareURL");
+    let viewPromise = promisePageActionPanelShown();
+    EventUtils.synthesizeMouseAtCenter(shareButton, {});
+    await viewPromise;
+
+    // Ensure we have share providers
+    let panel = document.getElementById("pageAction-urlbar-shareURL-subview-body");
+    Assert.equal(panel.childNodes.length, 1, "Has correct share receivers");
+
+    // Remove the Share URL button from the Address bar so we dont interfere
+    // with future tests
+    contextMenuPromise = promisePanelShown("pageActionContextMenu");
+    EventUtils.synthesizeMouseAtCenter(shareButton, {
+      type: "contextmenu",
+      button: 2,
+    });
+    await contextMenuPromise;
+
+    contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+    ctxMenuButton = document.querySelector("#pageActionContextMenu " +
+                                           ".pageActionContextMenuItem");
+    EventUtils.synthesizeMouseAtCenter(ctxMenuButton, {});
+    await contextMenuPromise;
+  });
+});
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -250,16 +250,17 @@ file, You can obtain one at http://mozil
             this.removeAttribute("actiontype");
           }
           return returnValue;
         ]]></body>
       </method>
 
       <method name="onKeyPress">
         <parameter name="aEvent"/>
+        <parameter name="aNoDefer"/>
         <body><![CDATA[
           switch (aEvent.keyCode) {
             case KeyEvent.DOM_VK_LEFT:
             case KeyEvent.DOM_VK_RIGHT:
             case KeyEvent.DOM_VK_HOME:
               // Reset the selected index so that nsAutoCompleteController
               // simply closes the popup without trying to fill anything.
               this.popup.selectedIndex = -1;
@@ -271,17 +272,17 @@ file, You can obtain one at http://mozil
             case KeyEvent.DOM_VK_DOWN:
             case KeyEvent.DOM_VK_PAGE_UP:
             case KeyEvent.DOM_VK_PAGE_DOWN:
               if (this.userSelectionBehavior != "tab")
                 this.userSelectionBehavior = "arrow";
               break;
           }
           if (!this.popup.disableKeyNavigation) {
-            if (this._shouldDeferKeyEvent(aEvent)) {
+            if (!aNoDefer && this._shouldDeferKeyEvent(aEvent)) {
               this._deferKeyEvent(aEvent, "onKeyPress");
               return false;
             }
             if (this.popup.popupOpen && this.popup.handleKeyPress(aEvent)) {
               return true;
             }
           }
           return this.handleKeyPress(aEvent);
@@ -304,19 +305,19 @@ file, You can obtain one at http://mozil
                 The key event that should maybe be deferred.
         @return True if the event should be deferred, false if not.
        -->
       <method name="_shouldDeferKeyEvent">
         <parameter name="event"/>
         <body><![CDATA[
           // If any event has been deferred for this search, then defer all
           // subsequent events so that the user does not experience any
-          // keypresses out of order.  All events will be replayed when this
-          // timeout fires.
-          if (this._deferredKeyEventTimeout) {
+          // keypresses out of order.  All events will be replayed when
+          // _deferredKeyEventTimeout fires.
+          if (this._deferredKeyEventQueue.length) {
             return true;
           }
 
           // At this point, no events have been deferred for this search, and we
           // need to decide whether `event` is the first one that should be.
 
           if (!this._keyCodesToDefer.has(event.keyCode)) {
             // Not a key that should trigger deferring.
@@ -351,16 +352,21 @@ file, You can obtain one at http://mozil
 
         @param  event
                 The key event.
         @return True if the event can be played, false if not.
       -->
       <method name="_safeToPlayDeferredKeyEvent">
         <parameter name="event"/>
         <body><![CDATA[
+          if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
+            return this.popup.selectedIndex != 0 ||
+                   this.gotResultForCurrentQuery;
+          }
+
           if (!this.gotResultForCurrentQuery || !this.popupOpen) {
             // We're still waiting on the first result, or the popup hasn't
             // opened yet, so not safe.
             return false;
           }
 
           let maxResultsRemaining =
             this.popup.maxResults - this.popup.matchCount;
@@ -385,28 +391,33 @@ file, You can obtain one at http://mozil
 
       <!--
         Adds a key event to the deferred event queue.
 
         @param event
                The key event to defer.
         @param methodName
                The name of the method on `this` to call.  It's expected to take
-               a single argument, the event.
+               two arguments: the event, and a noDefer bool.  If the bool is
+               true, then the event is being replayed and it should not be
+               deferred.
       -->
       <method name="_deferKeyEvent">
         <parameter name="event"/>
         <parameter name="methodName"/>
         <body><![CDATA[
           // Somehow event.defaultPrevented ends up true for deferred events.
           // autocomplete ignores defaultPrevented events, which means it would
           // ignore replayed deferred events if we didn't tell it to bypass
           // defaultPrevented.  That's the purpose of this expando.  If we could
           // figure out what's setting defaultPrevented and prevent it, then we
           // could get rid of this.
+          if (event.urlbarDeferred) {
+            throw new Error("Key event already deferred!");
+          }
           event.urlbarDeferred = true;
 
           this._deferredKeyEventQueue.push({
             methodName,
             event,
             searchString: this.mController.searchString,
           });
 
@@ -414,30 +425,27 @@ file, You can obtain one at http://mozil
             // Start the timeout that will unconditionally replay all deferred
             // events when it fires so that, after a certain point, we don't
             // keep blocking the user's keypresses when nothing else has caused
             // the events to be replayed.  Do not check whether it's safe to
             // replay the events because otherwise it may look like we ignored
             // the user's input.
             let elapsed = Cu.now() - this._searchStartDate;
             let remaining = this._deferredKeyEventTimeoutMs - elapsed;
-            if (remaining <= 0) {
+            this._deferredKeyEventTimeout = setTimeout(() => {
               this.replayAllDeferredKeyEvents();
-            } else {
-              this._deferredKeyEventTimeout = setTimeout(() => {
-                this.replayAllDeferredKeyEvents();
-                this._deferredKeyEventTimeout = null;
-              }, remaining);
-            }
+              this._deferredKeyEventTimeout = null;
+            }, Math.max(0, remaining));
           }
         ]]></body>
       </method>
 
       <!-- The enter key is always deferred, so it's not included here. -->
       <field name="_keyCodesToDefer">new Set([
+        KeyboardEvent.DOM_VK_RETURN,
         KeyboardEvent.DOM_VK_DOWN,
         KeyboardEvent.DOM_VK_TAB,
       ])</field>
       <field name="_deferredKeyEventQueue">[]</field>
       <field name="_deferredKeyEventTimeout">null</field>
       <field name="_deferredKeyEventTimeoutMs">200</field>
       <field name="_searchStartDate">0</field>
 
@@ -477,17 +485,17 @@ file, You can obtain one at http://mozil
         ]]></body>
       </method>
 
       <method name="_replayKeyEventInstance">
         <parameter name="instance"/>
         <body><![CDATA[
           // Safety check: handle only if the search string didn't change.
           if (this.mController.searchString == instance.searchString) {
-            this[instance.methodName](instance.event);
+            this[instance.methodName](instance.event, true);
           }
         ]]></body>
       </method>
 
       <field name="_mayTrimURLs">true</field>
       <method name="trimValue">
         <parameter name="aURL"/>
         <body><![CDATA[
@@ -1476,16 +1484,17 @@ file, You can obtain one at http://mozil
             }
           }
           this.resetActionType();
         ]]></body>
       </method>
 
       <method name="handleEnter">
         <parameter name="event"/>
+        <parameter name="noDefer"/>
         <body><![CDATA[
           // We need to ensure we're using a selected autocomplete result.
           // A result should automatically be selected by default,
           // however autocomplete is async and therefore we may not
           // have a result set relating to the current input yet. If that
           // happens, we need to mark that when the first result does get added,
           // it needs to be handled as if enter was pressed with that first
           // result selected.
@@ -1495,40 +1504,39 @@ file, You can obtain one at http://mozil
           // input.
           // However, if the default result is automatically selected, we
           // ensure that it corresponds to the current input.
 
           // Store the current search string so it can be used in handleCommand,
           // which will be called as a result of mController.handleEnter().
           this.handleEnterSearchString = this.mController.searchString;
 
-          if (!this._deferredKeyEventQueue.length &&
-              (this.popup.selectedIndex != 0 || this.gotResultForCurrentQuery)) {
-            let canonizeValue = this.value;
-            if (event.shiftKey || (AppConstants.platform === "macosx" ?
-                                   event.metaKey :
-                                   event.ctrlKey)) {
-              let action = this._parseActionUrl(canonizeValue);
-              if (action && "searchSuggestion" in action.params) {
-                canonizeValue = action.params.searchSuggestion;
-              } else if (this.popup.selectedIndex === 0 &&
-                         this.mController.getStyleAt(0).includes("autofill")) {
-                canonizeValue = this.handleEnterSearchString;
-              }
-            }
-            this.maybeCanonizeURL(event, canonizeValue);
-            let handled = this.mController.handleEnter(false, event);
-            this.handleEnterSearchString = null;
-            this.popup.overrideValue = null;
-            return handled;
+          if (!noDefer && this._shouldDeferKeyEvent(event)) {
+            // Defer the event until the first non-heuristic result comes in.
+            this._deferKeyEvent(event, "handleEnter");
+            return false;
           }
 
-          // Defer the event until the first non-heuristic result comes in.
-          this._deferKeyEvent(event, "handleEnter");
-          return false;
+          let canonizeValue = this.value;
+          if (event.shiftKey || (AppConstants.platform === "macosx" ?
+                                 event.metaKey :
+                                 event.ctrlKey)) {
+            let action = this._parseActionUrl(canonizeValue);
+            if (action && "searchSuggestion" in action.params) {
+              canonizeValue = action.params.searchSuggestion;
+            } else if (this.popup.selectedIndex === 0 &&
+                       this.mController.getStyleAt(0).includes("autofill")) {
+              canonizeValue = this.handleEnterSearchString;
+            }
+          }
+          this.maybeCanonizeURL(event, canonizeValue);
+          let handled = this.mController.handleEnter(false, event);
+          this.handleEnterSearchString = null;
+          this.popup.overrideValue = null;
+          return handled;
         ]]></body>
       </method>
 
       <method name="handleDelete">
         <body><![CDATA[
           // If the heuristic result is selected, then the autocomplete
           // controller's handleDelete implementation will remove it, which is
           // not what we want.  So in that case, call handleText so it acts as
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -6,24 +6,21 @@
 // Services = object with smart getters for common XPCOM services
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
-
-ChromeUtils.defineModuleGetter(this, "ShellService",
-                               "resource:///modules/ShellService.jsm");
-
-ChromeUtils.defineModuleGetter(this, "ContextualIdentityService",
-                               "resource://gre/modules/ContextualIdentityService.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
+  ShellService: "resource:///modules/ShellService.jsm"
+});
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 Object.defineProperty(this, "BROWSER_NEW_TAB_URL", {
   configurable: true,
   enumerable: true,
@@ -57,19 +54,20 @@ function getBrowserURL() {
 function getTopWin(skipPopups) {
   // If this is called in a browser window, use that window regardless of
   // whether it's the frontmost window, since commands can be executed in
   // background windows (bug 626148).
   if (top.document.documentElement.getAttribute("windowtype") == "navigator:browser" &&
       (!skipPopups || top.toolbar.visible))
     return top;
 
-  let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
-  return RecentWindow.getMostRecentBrowserWindow({private: isPrivate,
-                                                  allowPopups: !skipPopups});
+  return BrowserWindowTracker.getTopWindow({
+    private: PrivateBrowsingUtils.isWindowPrivate(window),
+    allowPopups: !skipPopups
+  });
 }
 
 function getBoolPref(prefname, def) {
   try {
     return Services.prefs.getBoolPref(prefname);
   } catch (er) {
     return def;
   }
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -34,24 +34,24 @@ var EXPORTED_SYMBOLS = [
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   PluralForm: "resource://gre/modules/PluralForm.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   DownloadHistory: "resource://gre/modules/DownloadHistory.jsm",
   Downloads: "resource://gre/modules/Downloads.jsm",
   DownloadUIHelper: "resource://gre/modules/DownloadUIHelper.jsm",
   DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",
   FileUtils: "resource://gre/modules/FileUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
-  RecentWindow: "resource:///modules/RecentWindow.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "DownloadsLogger", () => {
   let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
   let consoleOptions = {
     maxLogLevelPref: "browser.download.loglevel",
     prefix: "Downloads"
   };
@@ -821,17 +821,17 @@ DownloadsDataCtor.prototype = {
    *
    * @param aType
    *        Set to "start" for new downloads, "finish" for completed downloads.
    */
   _notifyDownloadEvent(aType) {
     DownloadsCommon.log("Attempting to notify that a new download has started or finished.");
 
     // Show the panel in the most recent browser window, if present.
-    let browserWin = RecentWindow.getMostRecentBrowserWindow({ private: this._isPrivate });
+    let browserWin = BrowserWindowTracker.getTopWindow({ private: this._isPrivate });
     if (!browserWin) {
       return;
     }
 
     if (this.panelHasShownBefore) {
       // For new downloads after the first one, don't show the panel
       // automatically, but provide a visible notification in the topmost
       // browser window, if the status indicator is already visible.
--- a/browser/components/downloads/DownloadsTaskbar.jsm
+++ b/browser/components/downloads/DownloadsTaskbar.jsm
@@ -12,23 +12,21 @@
 
 var EXPORTED_SYMBOLS = [
   "DownloadsTaskbar",
 ];
 
 // Globals
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-ChromeUtils.defineModuleGetter(this, "Downloads",
-                               "resource://gre/modules/Downloads.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
-ChromeUtils.defineModuleGetter(this, "Services",
-                               "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  Downloads: "resource://gre/modules/Downloads.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+});
 
 XPCOMUtils.defineLazyGetter(this, "gWinTaskbar", function() {
   if (!("@mozilla.org/windows-taskbar;1" in Cc)) {
     return null;
   }
   let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"]
                      .getService(Ci.nsIWinTaskbar);
   return winTaskbar.available && winTaskbar;
@@ -135,17 +133,17 @@ var DownloadsTaskbar = {
     // the state of the new indicator, otherwise it will be updated as soon as
     // the DownloadSummary view is registered.
     if (this._summary) {
       this.onSummaryChanged();
     }
 
     aWindow.addEventListener("unload", () => {
       // Locate another browser window, excluding the one being closed.
-      let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+      let browserWindow = BrowserWindowTracker.getTopWindow();
       if (browserWindow) {
         // Move the progress indicator to the other browser window.
         this._attachIndicator(browserWindow);
       } else {
         // The last browser window has been closed.  We remove the reference to
         // the taskbar progress object so that the indicator will be registered
         // again on the next browser window that is opened.
         this._taskbarProgress = null;
@@ -164,17 +162,17 @@ var DownloadsTaskbar = {
     // the state of the new indicator, otherwise it will be updated as soon as
     // the DownloadSummary view is registered.
     if (this._summary) {
       this.onSummaryChanged();
     }
 
     aWindow.addEventListener("unload", () => {
       // Locate another browser window, excluding the one being closed.
-      let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+      let browserWindow = BrowserWindowTracker.getTopWindow();
       if (browserWindow) {
         // Move the progress indicator to the other browser window.
         this._attachGtkTaskbarProgress(browserWindow);
       } else {
         // The last browser window has been closed.  We remove the reference to
         // the taskbar progress object so that the indicator will be registered
         // again on the next browser window that is opened.
         this._taskbarProgress = null;
--- a/browser/components/downloads/DownloadsViewUI.jsm
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -10,30 +10,25 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = [
   "DownloadsViewUI",
 ];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "Downloads",
-                               "resource://gre/modules/Downloads.jsm");
-ChromeUtils.defineModuleGetter(this, "DownloadUtils",
-                               "resource://gre/modules/DownloadUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "DownloadsCommon",
-                               "resource:///modules/DownloadsCommon.jsm");
-ChromeUtils.defineModuleGetter(this, "FileUtils",
-                               "resource://gre/modules/FileUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "OS",
-                               "resource://gre/modules/osfile.jsm");
-ChromeUtils.defineModuleGetter(this, "PlacesUtils",
-                               "resource://gre/modules/PlacesUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  Downloads: "resource://gre/modules/Downloads.jsm",
+  DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",
+  DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
+  FileUtils: "resource://gre/modules/FileUtils.jsm",
+  OS: "resource://gre/modules/osfile.jsm",
+  PlacesUtils: "resource://gre/modules/PlacesUtils.jsm"
+});
 
 var DownloadsViewUI = {
   /**
    * Returns true if the given string is the name of a command that can be
    * handled by the Downloads user interface, including standard commands.
    */
   isCommandName(name) {
     return name.startsWith("cmd_") || name.startsWith("downloadsCmd_");
@@ -127,17 +122,17 @@ this.DownloadsViewUI.DownloadElementShel
       // History downloads may not have a size defined.
       sizeStrings.stateLabel = s.sizeUnknown;
       sizeStrings.status = s.stateCompleted;
     }
     return sizeStrings;
   },
 
   get browserWindow() {
-    return RecentWindow.getMostRecentBrowserWindow();
+    return BrowserWindowTracker.getTopWindow();
   },
 
   /**
    * The progress element for the download, or undefined in case the XBL binding
    * has not been applied yet.
    */
   get _progressElement() {
     if (!this.__progressElement) {
--- a/browser/components/downloads/content/allDownloadsView.js
+++ b/browser/components/downloads/content/allDownloadsView.js
@@ -1,31 +1,25 @@
 /* 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/. */
 /* eslint-env mozilla/browser-window */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "Downloads",
-                               "resource://gre/modules/Downloads.jsm");
-ChromeUtils.defineModuleGetter(this, "DownloadsCommon",
-                               "resource:///modules/DownloadsCommon.jsm");
-ChromeUtils.defineModuleGetter(this, "DownloadsViewUI",
-                               "resource:///modules/DownloadsViewUI.jsm");
-ChromeUtils.defineModuleGetter(this, "FileUtils",
-                               "resource://gre/modules/FileUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "NetUtil",
-                               "resource://gre/modules/NetUtil.jsm");
-ChromeUtils.defineModuleGetter(this, "OS",
-                               "resource://gre/modules/osfile.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
-ChromeUtils.defineModuleGetter(this, "Services",
-                               "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  Downloads: "resource://gre/modules/Downloads.jsm",
+  DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
+  DownloadsViewUI: "resource:///modules/DownloadsViewUI.jsm",
+  FileUtils: "resource://gre/modules/FileUtils.jsm",
+  NetUtil: "resource://gre/modules/NetUtil.jsm",
+  OS: "resource://gre/modules/osfile.jsm",
+  Services: "resource://gre/modules/Services.jsm"
+});
 
 /**
  * A download element shell is responsible for handling the commands and the
  * displayed data for a single download view element.
  *
  * The shell may contain a session download, a history download, or both.  When
  * both a history and a session download are present, the session download gets
  * priority and its information is displayed.
@@ -567,17 +561,17 @@ DownloadsPlacesView.prototype = {
 
   _canDownloadClipboardURL() {
     let [url /* ,name */] = this._getURLFromClipboardData();
     return url != "";
   },
 
   _downloadURLFromClipboard() {
     let [url, name] = this._getURLFromClipboardData();
-    let browserWin = RecentWindow.getMostRecentBrowserWindow();
+    let browserWin = BrowserWindowTracker.getTopWindow();
     let initiatingDoc = browserWin ? browserWin.document : document;
     DownloadURL(url, name, initiatingDoc);
   },
 
   // nsIController
   doCommand(aCommand) {
     // Commands may be invoked with keyboard shortcuts even if disabled.
     if (!this.isCommandEnabled(aCommand)) {
@@ -747,17 +741,17 @@ DownloadsPlacesView.prototype = {
     // redownload already downloaded file.
     if (dt.mozGetDataAt("application/x-moz-file", 0)) {
       return;
     }
 
     let links = Services.droppedLinkHandler.dropLinks(aEvent);
     if (!links.length)
       return;
-    let browserWin = RecentWindow.getMostRecentBrowserWindow();
+    let browserWin = BrowserWindowTracker.getTopWindow();
     let initiatingDoc = browserWin ? browserWin.document : document;
     for (let link of links) {
       if (link.url.startsWith("about:"))
         continue;
       DownloadURL(link.url, link.name, initiatingDoc);
     }
   },
 };
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -3,18 +3,18 @@
 "use strict";
 
 // This file provides some useful code for the |tabs| and |windows|
 // modules. All of the code is installed on |global|, which is a scope
 // shared among the different ext-*.js scripts.
 
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
+ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
+                               "resource:///modules/BrowserWindowTracker.jsm");
 
 var {
   ExtensionError,
   defineLazyGetter,
 } = ExtensionUtils;
 
 const READER_MODE_PREFIX = "about:reader";
 
@@ -183,17 +183,17 @@ class WindowTracker extends WindowTracke
   /**
    * @property {DOMWindow|null} topNormalWindow
    *        The currently active, or topmost, browser window, or null if no
    *        browser window is currently open.
    *        Will return the topmost "normal" (i.e., not popup) window.
    *        @readonly
    */
   get topNormalWindow() {
-    return RecentWindow.getMostRecentBrowserWindow({allowPopups: false});
+    return BrowserWindowTracker.getTopWindow({allowPopups: false});
   }
 }
 
 class TabTracker extends TabTrackerBase {
   constructor() {
     super();
 
     this._tabs = new WeakMap();
--- a/browser/components/extensions/test/browser/browser_ext_tabs_lazy.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_lazy.js
@@ -9,16 +9,17 @@ const SESSION = {
       {entries: [{url: "about:blank", triggeringPrincipal_base64}]},
       {entries: [{url: "https://example.com/", triggeringPrincipal_base64}]},
     ],
   }],
 };
 
 add_task(async function() {
   SessionStore.setBrowserState(JSON.stringify(SESSION));
+  await promiseWindowRestored(window);
   const tab = gBrowser.tabs[1];
 
   is(tab.getAttribute("pending"), "true", "The tab is pending restore");
   is(tab.linkedBrowser.isConnected, false, "The tab is lazy");
 
   async function background() {
     const [tab] = await browser.tabs.query({url: "https://example.com/"});
     browser.test.assertRejects(
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -16,17 +16,17 @@
  *          openToolsMenu closeToolsMenu
  *          imageBuffer imageBufferFromDataURI
  *          getListStyleImage getPanelForNode
  *          awaitExtensionPanel awaitPopupResize
  *          promiseContentDimensions alterContent
  *          promisePrefChangeObserved openContextMenuInFrame
  *          promiseAnimationFrame getCustomizableUIPanelID
  *          awaitEvent BrowserWindowIterator
- *          navigateTab historyPushState
+ *          navigateTab historyPushState promiseWindowRestored
  */
 
 // There are shutdown issues for which multiple rejections are left uncaught.
 // This bug should be fixed, but for the moment this directory is whitelisted.
 //
 // NOTE: Entire directory whitelisting should be kept to a minimum. Normally you
 //       should use "expectUncaughtRejection" to flag individual failures.
 const {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", {});
@@ -483,16 +483,20 @@ function closePageAction(extension, win 
 function promisePrefChangeObserved(pref) {
   return new Promise((resolve, reject) =>
     Preferences.observe(pref, function prefObserver() {
       Preferences.ignore(pref, prefObserver);
       resolve();
     }));
 }
 
+function promiseWindowRestored(window) {
+  return new Promise(resolve => window.addEventListener("SSWindowRestored", resolve, {once: true}));
+}
+
 function awaitEvent(eventName, id) {
   return new Promise(resolve => {
     let listener = (_eventName, ...args) => {
       let extension = args[0];
       if (_eventName === eventName &&
           extension.id == id) {
         Management.off(eventName, listener);
         resolve();
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -1,30 +1,26 @@
 /* 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/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
-ChromeUtils.defineModuleGetter(this, "HeadlessShell",
-                               "resource:///modules/HeadlessShell.jsm");
-ChromeUtils.defineModuleGetter(this, "LaterRun",
-                               "resource:///modules/LaterRun.jsm");
-ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
-                               "resource://gre/modules/PrivateBrowsingUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
-ChromeUtils.defineModuleGetter(this, "ShellService",
-                               "resource:///modules/ShellService.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  HeadlessShell: "resource:///modules/HeadlessShell.jsm",
+  LaterRun: "resource:///modules/LaterRun.jsm",
+  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
+  ShellService: "resource:///modules/ShellService.jsm",
+  UpdatePing: "resource://gre/modules/UpdatePing.jsm"
+});
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
-                                   "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
-ChromeUtils.defineModuleGetter(this, "UpdatePing",
-                               "resource://gre/modules/UpdatePing.jsm");
+  "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
 
 function shouldLoadURI(aURI) {
   if (aURI && !aURI.schemeIs("chrome"))
     return true;
 
   dump("*** Preventing external load of chrome: URI into browser window\n");
   dump("    Use --chrome <uri> instead\n");
   return false;
@@ -672,17 +668,17 @@ var gBrowserContentHandler = new nsBrows
 
 function handURIToExistingBrowser(uri, location, cmdLine, forcePrivate, triggeringPrincipal) {
   if (!shouldLoadURI(uri))
     return;
 
   // Unless using a private window is forced, open external links in private
   // windows only if we're in perma-private mode.
   var allowPrivate = forcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
-  var navWin = RecentWindow.getMostRecentBrowserWindow({private: allowPrivate});
+  var navWin = BrowserWindowTracker.getTopWindow({private: allowPrivate});
   if (!navWin) {
     // if we couldn't load it in an existing window, open a new one
     openBrowserWindow(cmdLine, uri.spec, null, forcePrivate);
     return;
   }
 
   var navNav = navWin.QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIWebNavigation);
@@ -784,17 +780,17 @@ nsDefaultCommandLineHandler.prototype = 
         openBrowserWindow(cmdLine, URLlist);
       }
 
     } else if (!cmdLine.preventDefault) {
       if (AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
           cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
           WindowsUIUtils.inTabletMode) {
         // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
-        let win = RecentWindow.getMostRecentBrowserWindow();
+        let win = BrowserWindowTracker.getTopWindow();
         if (win) {
           win.focus();
           return;
         }
       }
       openBrowserWindow(cmdLine);
     } else {
       // Need a better solution in the future to avoid opening the blank window
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -91,16 +91,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AsyncPrefs: "resource://gre/modules/AsyncPrefs.jsm",
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   AutoCompletePopup: "resource://gre/modules/AutoCompletePopup.jsm",
   BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.jsm",
   BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.jsm",
   BrowserErrorReporter: "resource:///modules/BrowserErrorReporter.jsm",
   BrowserUITelemetry: "resource:///modules/BrowserUITelemetry.jsm",
   BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   ContentClick: "resource:///modules/ContentClick.jsm",
   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
   CustomizableUI: "resource:///modules/CustomizableUI.jsm",
   DateTimePickerHelper: "resource://gre/modules/DateTimePickerHelper.jsm",
   ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
   Feeds: "resource:///modules/Feeds.jsm",
   FileSource: "resource://gre/modules/L10nRegistry.jsm",
   FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
@@ -122,17 +123,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   PermissionUI: "resource:///modules/PermissionUI.jsm",
   PingCentre: "resource:///modules/PingCentre.jsm",
   PlacesBackups: "resource://gre/modules/PlacesBackups.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   PluralForm: "resource://gre/modules/PluralForm.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm",
   ReaderParent: "resource:///modules/ReaderParent.jsm",
-  RecentWindow: "resource:///modules/RecentWindow.jsm",
   RemotePrompt: "resource:///modules/RemotePrompt.jsm",
   SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
   Sanitizer: "resource:///modules/Sanitizer.jsm",
   SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
   ShellService: "resource:///modules/ShellService.jsm",
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
   UIState: "resource://services-sync/UIState.jsm",
   UITour: "resource:///modules/UITour.jsm",
@@ -500,17 +500,17 @@ BrowserGlue.prototype = {
         this._migrationImportsDefaultBookmarks = true;
         break;
       case "initial-migration-did-import-default-bookmarks":
         this._initPlaces(true);
         break;
       case "handle-xul-text-link":
         let linkHandled = subject.QueryInterface(Ci.nsISupportsPRBool);
         if (!linkHandled.data) {
-          let win = RecentWindow.getMostRecentBrowserWindow();
+          let win = BrowserWindowTracker.getTopWindow();
           if (win) {
             data = JSON.parse(data);
             let where = win.whereToOpenLink(data);
             // Preserve legacy behavior of non-modifier left-clicks
             // opening in a new selected tab.
             if (where == "current") {
               where = "tab";
             }
@@ -529,17 +529,17 @@ BrowserGlue.prototype = {
         // This notification is broadcast by the docshell when it "fixes up" a
         // URI that it's been asked to load into a keyword search.
         let engine = null;
         try {
           engine = subject.QueryInterface(Ci.nsISearchEngine);
         } catch (ex) {
           Cu.reportError(ex);
         }
-        let win = RecentWindow.getMostRecentBrowserWindow();
+        let win = BrowserWindowTracker.getTopWindow();
         win.BrowserSearch.recordSearchInTelemetry(engine, "urlbar");
         break;
       case "browser-search-engine-modified":
         // Ensure we cleanup the hiddenOneOffs pref when removing
         // an engine, and that newly added engines are visible.
         if (data == "engine-added" || data == "engine-removed") {
           let engineName = subject.QueryInterface(Ci.nsISearchEngine).name;
           let pref = Services.prefs.getStringPref("browser.search.hiddenOneOffs");
@@ -827,17 +827,17 @@ BrowserGlue.prototype = {
     const ONE_DAY = 24 * 60 * 60 * 1000;
     return (Date.now() - profileDate) / ONE_DAY;
   },
 
   _showSlowStartupNotification(profileAge) {
     if (profileAge < 90) // 3 months
       return;
 
-    let win = RecentWindow.getMostRecentBrowserWindow();
+    let win = BrowserWindowTracker.getTopWindow();
     if (!win)
       return;
 
     let productName = gBrandBundle.GetStringFromName("brandFullName");
     let message = win.gNavigatorBundle.getFormattedString("slowStartup.message", [productName]);
 
     let buttons = [
       {
@@ -865,17 +865,17 @@ BrowserGlue.prototype = {
   /**
    * Show a notification bar offering a reset.
    *
    * @param reason
    *        String of either "unused" or "uninstall", specifying the reason
    *        why a profile reset is offered.
    */
   _resetProfileNotification(reason) {
-    let win = RecentWindow.getMostRecentBrowserWindow();
+    let win = BrowserWindowTracker.getTopWindow();
     if (!win)
       return;
 
     ChromeUtils.import("resource://gre/modules/ResetProfile.jsm");
     if (!ResetProfile.resetSupported())
       return;
 
     let productName = gBrandBundle.GetStringFromName("brandShortName");
@@ -902,17 +902,17 @@ BrowserGlue.prototype = {
 
     let nb = win.document.getElementById("global-notificationbox");
     nb.appendNotification(message, "reset-profile-notification",
                           "chrome://global/skin/icons/question-16.png",
                           nb.PRIORITY_INFO_LOW, buttons);
   },
 
   _notifyUnsignedAddonsDisabled() {
-    let win = RecentWindow.getMostRecentBrowserWindow();
+    let win = BrowserWindowTracker.getTopWindow();
     if (!win)
       return;
 
     let message = win.gNavigatorBundle.getString("unsignedAddonsDisabled.message");
     let buttons = [
       {
         label:     win.gNavigatorBundle.getString("unsignedAddonsDisabled.learnMore.label"),
         accessKey: win.gNavigatorBundle.getString("unsignedAddonsDisabled.learnMore.accesskey"),
@@ -1476,17 +1476,17 @@ BrowserGlue.prototype = {
                                   stringParams: [appName]});
       let url = getNotifyString({propName: "notificationURL",
                                  prefName: "startup.homepage_override_url"});
       let label = getNotifyString({propName: "notificationButtonLabel",
                                    stringName: "pu.notifyButton.label"});
       let key = getNotifyString({propName: "notificationButtonAccessKey",
                                  stringName: "pu.notifyButton.accesskey"});
 
-      let win = RecentWindow.getMostRecentBrowserWindow();
+      let win = BrowserWindowTracker.getTopWindow();
       let notifyBox = win.document.getElementById("high-priority-global-notificationbox");
 
       let buttons = [
                       {
                         label,
                         accessKey: key,
                         popup:     null,
                         callback(aNotificationBar, aButton) {
@@ -1511,17 +1511,17 @@ BrowserGlue.prototype = {
                                 stringParams: [appName]});
     let url = getNotifyString({propName: "alertURL",
                                prefName: "startup.homepage_override_url"});
 
     function clickCallback(subject, topic, data) {
       // This callback will be called twice but only once with this topic
       if (topic != "alertclickcallback")
         return;
-      let win = RecentWindow.getMostRecentBrowserWindow();
+      let win = BrowserWindowTracker.getTopWindow();
       win.openTrustedLinkIn(data, "tab");
     }
 
     try {
       // This will throw NS_ERROR_NOT_AVAILABLE if the notification cannot
       // be displayed per the idl.
       this.AlertsService.showAlertNotification(null, title, text,
                                           true, url, clickCallback);
@@ -1772,17 +1772,17 @@ BrowserGlue.prototype = {
     var text = placesBundle.formatStringFromName("lockPrompt.text", [applicationName], 1);
     var buttonText = placesBundle.GetStringFromName("lockPromptInfoButton.label");
     var accessKey = placesBundle.GetStringFromName("lockPromptInfoButton.accessKey");
 
     var helpTopic = "places-locked";
     var url = Services.urlFormatter.formatURLPref("app.support.baseURL");
     url += helpTopic;
 
-    var win = RecentWindow.getMostRecentBrowserWindow();
+    var win = BrowserWindowTracker.getTopWindow();
 
     var buttons = [
                     {
                       label:     buttonText,
                       accessKey,
                       popup:     null,
                       callback(aNotificationBar, aButton) {
                         win.openTrustedLinkIn(url, "tab");
@@ -2279,17 +2279,17 @@ BrowserGlue.prototype = {
                         .add(isDefaultError);
       Services.telemetry.getHistogramById("BROWSER_SET_DEFAULT_ALWAYS_CHECK")
                         .add(shouldCheck);
       Services.telemetry.getHistogramById("BROWSER_SET_DEFAULT_DIALOG_PROMPT_RAWCOUNT")
                         .add(promptCount);
     } catch (ex) { /* Don't break the default prompt if telemetry is broken. */ }
 
     if (willPrompt) {
-      DefaultBrowserCheck.prompt(RecentWindow.getMostRecentBrowserWindow());
+      DefaultBrowserCheck.prompt(BrowserWindowTracker.getTopWindow());
     }
   },
 
   async _migrateMatchBucketsPrefForUI66() {
     // This does two related things.
     //
     // (1) Profiles created on or after Firefox 57's release date were eligible
     // for a Shield study that changed the browser.urlbar.matchBuckets pref in
@@ -2501,17 +2501,17 @@ BrowserGlue.prototype = {
    * Open preferences even if there are no open windows.
    */
   _openPreferences(...args) {
     if (Services.appShell.hiddenDOMWindow.openPreferences) {
       Services.appShell.hiddenDOMWindow.openPreferences(...args);
       return;
     }
 
-    let chromeWindow = RecentWindow.getMostRecentBrowserWindow();
+    let chromeWindow = BrowserWindowTracker.getTopWindow();
     chromeWindow.openPreferences(...args);
   },
 
   _openURLInNewWindow(url) {
     let urlString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
     urlString.data = url;
     return new Promise(resolve => {
       let win = Services.ww.openWindow(null, Services.prefs.getCharPref("browser.chromeURL"),
@@ -2526,17 +2526,17 @@ BrowserGlue.prototype = {
    * We open the received URIs in background tabs.
    */
   async _onDisplaySyncURIs(data) {
     try {
       // The payload is wrapped weirdly because of how Sync does notifications.
       const URIs = data.wrappedJSObject.object;
 
       // win can be null, but it's ok, we'll assign it later in openTab()
-      let win = RecentWindow.getMostRecentBrowserWindow({private: false});
+      let win = BrowserWindowTracker.getTopWindow({private: false});
 
       const openTab = async (URI) => {
         let tab;
         if (!win) {
           win = await this._openURLInNewWindow(URI.uri);
           let tabs = win.gBrowser.tabs;
           tab = tabs[tabs.length - 1];
         } else {
@@ -2609,17 +2609,17 @@ BrowserGlue.prototype = {
   },
 
   async _onVerifyLoginNotification({body, title, url}) {
     let tab;
     let imageURL;
     if (AppConstants.platform == "win") {
       imageURL = "chrome://branding/content/icon64.png";
     }
-    let win = RecentWindow.getMostRecentBrowserWindow({private: false});
+    let win = BrowserWindowTracker.getTopWindow({private: false});
     if (!win) {
       win = await this._openURLInNewWindow(url);
       let tabs = win.gBrowser.tabs;
       tab = tabs[tabs.length - 1];
     } else {
       tab = win.gBrowser.addTab(url);
     }
     tab.setAttribute("attention", true);
@@ -2644,17 +2644,17 @@ BrowserGlue.prototype = {
     let body = accountsBundle.formatStringFromName("deviceConnectedBody" +
                                                    (deviceName ? "" : ".noDeviceName"),
                                                    [deviceName], 1);
 
     let clickCallback = async (subject, topic, data) => {
       if (topic != "alertclickcallback")
         return;
       let url = await FxAccounts.config.promiseManageDevicesURI("device-connected-notification");
-      let win = RecentWindow.getMostRecentBrowserWindow({private: false});
+      let win = BrowserWindowTracker.getTopWindow({private: false});
       if (!win) {
         this._openURLInNewWindow(url);
       } else {
         win.gBrowser.addTab(url);
       }
     };
 
     try {
@@ -2694,17 +2694,17 @@ BrowserGlue.prototype = {
       return;
     }
     if (Services.prefs.getBoolPref("browser.flash-protected-mode-flip.done")) {
       return;
     }
     Services.prefs.setBoolPref("dom.ipc.plugins.flash.disable-protected-mode", true);
     Services.prefs.setBoolPref("browser.flash-protected-mode-flip.done", true);
 
-    let win = RecentWindow.getMostRecentBrowserWindow();
+    let win = BrowserWindowTracker.getTopWindow();
     if (!win) {
       return;
     }
     let productName = gBrandBundle.GetStringFromName("brandShortName");
     let message = win.gNavigatorBundle.
       getFormattedString("flashHang.message", [productName]);
     let buttons = [{
       label: win.gNavigatorBundle.getString("flashHang.helpButton.label"),
@@ -3061,17 +3061,17 @@ var JawsScreenReaderVersionCheck = {
     // support and never prompt if e10s is disabled or if we're on
     // nightly.
     if (!Services.appinfo.shouldBlockIncompatJaws ||
         !Services.appinfo.browserTabsRemoteAutostart ||
         AppConstants.NIGHTLY_BUILD) {
       return;
     }
 
-    let win = RecentWindow.getMostRecentBrowserWindow();
+    let win = BrowserWindowTracker.getTopWindow();
     if (!win || !win.gBrowser || !win.gBrowser.selectedBrowser) {
       Services.console.logStringMessage(
           "Content access support for older versions of JAWS is disabled " +
           "due to compatibility issues with this version of Firefox.");
       this._prompted = false;
       return;
     }
 
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -6,23 +6,23 @@
 var EXPORTED_SYMBOLS = ["PlacesUIUtils"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppConstants: "resource://gre/modules/AppConstants.jsm",
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm",
   PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   PluralForm: "resource://gre/modules/PluralForm.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
-  RecentWindow: "resource:///modules/RecentWindow.jsm",
   Weave: "resource://services-sync/main.js",
 });
 
 XPCOMUtils.defineLazyGetter(this, "bundle", function() {
   return Services.strings.createBundle("chrome://browser/locale/places/places.properties");
 });
 
 const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
@@ -602,17 +602,17 @@ var PlacesUIUtils = {
     if (!aItemsToOpen.length)
       return;
 
     // Prefer the caller window if it's a browser window, otherwise use
     // the top browser window.
     var browserWindow = null;
     browserWindow =
       aWindow && aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser" ?
-      aWindow : RecentWindow.getMostRecentBrowserWindow();
+      aWindow : BrowserWindowTracker.getTopWindow();
 
     var urls = [];
     let skipMarking = browserWindow && PrivateBrowsingUtils.isWindowPrivate(browserWindow);
     for (let item of aItemsToOpen) {
       urls.push(item.uri);
       if (skipMarking) {
         continue;
       }
@@ -740,17 +740,17 @@ var PlacesUIUtils = {
           this.markPageAsTyped(aNode.uri);
       }
 
       // Check whether the node is a bookmark which should be opened as
       // a web panel
       if (aWhere == "current" && isBookmark) {
         if (PlacesUtils.annotations
                        .itemHasAnnotation(aNode.itemId, this.LOAD_IN_SIDEBAR_ANNO)) {
-          let browserWin = RecentWindow.getMostRecentBrowserWindow();
+          let browserWin = BrowserWindowTracker.getTopWindow();
           if (browserWin) {
             browserWin.openWebPanel(aNode.title, aNode.uri);
             return;
           }
         }
       }
 
       aWindow.openTrustedLinkIn(aNode.uri, aWhere, {
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -171,17 +171,19 @@ PlacesController.prototype = {
     case "placesCmd_new:separator":
       return this._canInsert() &&
              !PlacesUtils.asQuery(this._view.result.root).queryOptions.excludeItems &&
              this._view.result.sortingMode ==
                  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
     case "placesCmd_show:info": {
       let selectedNode = this._view.selectedNode;
       return selectedNode && (PlacesUtils.nodeIsTagQuery(selectedNode) ||
-                              PlacesUtils.nodeIsBookmark(selectedNode));
+                              PlacesUtils.nodeIsBookmark(selectedNode) ||
+                              (PlacesUtils.nodeIsFolder(selectedNode) &&
+                               !PlacesUtils.isQueryGeneratedFolder(selectedNode)));
     }
     case "placesCmd_reload": {
       // Livemark containers
       let selectedNode = this._view.selectedNode;
       return selectedNode && this.hasCachedLivemarkInfo(selectedNode);
     }
     case "placesCmd_sortBy:name": {
       let selectedNode = this._view.selectedNode;
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1784,17 +1784,17 @@ PlacesTreeView.prototype = {
     // * the left pane special folders and queries (those are place: uri
     //   bookmarks)
     // * separators
     //
     // Note that concrete itemIds aren't used intentionally.  For example, we
     // have no reason to disallow renaming a shortcut to the Bookmarks Toolbar,
     // except for the one under All Bookmarks.
     if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemGuid) ||
-        PlacesUtils.isQueryGeneratedFolder(itemGuid))
+        PlacesUtils.isQueryGeneratedFolder(node))
       return false;
 
     return true;
   },
 
   setCellText: function PTV_setCellText(aRow, aColumn, aText) {
     // We may only get here if the cell is editable.
     let node = this._rows[aRow];
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -25,16 +25,17 @@ support-files =
 [browser_bookmarks_sidebar_search.js]
 support-files =
   pageopeningwindow.html
 [browser_bookmarkProperties_addFolderDefaultButton.js]
 [browser_bookmarkProperties_addKeywordForThisSearch.js]
 [browser_bookmarkProperties_addLivemark.js]
 [browser_bookmarkProperties_bookmarkAllTabs.js]
 [browser_bookmarkProperties_cancel.js]
+[browser_bookmarkProperties_editFolder.js]
 [browser_bookmarkProperties_editTagContainer.js]
 [browser_bookmarkProperties_no_user_actions.js]
 [browser_bookmarkProperties_readOnlyRoot.js]
 [browser_bookmarksProperties.js]
 [browser_check_correct_controllers.js]
 [browser_click_bookmarks_on_toolbar.js]
 [browser_controller_onDrop_sidebar.js]
 [browser_controller_onDrop_tagFolder.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_editFolder.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the Properties dialog on a folder.
+
+add_task(async function() {
+  info("Bug 479348 - Properties on a root should be read-only.");
+
+  let bm = await PlacesUtils.bookmarks.insert({
+    title: "folder",
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid
+  });
+  registerCleanupFunction(async function() {
+    await PlacesUtils.bookmarks.remove(bm);
+  });
+
+  await withSidebarTree("bookmarks", async function(tree) {
+    // Select the new bookmark in the sidebar.
+    tree.selectItems([bm.guid]);
+    let folder = tree.selectedNode;
+    Assert.equal(folder.title, "folder", "Folder title is correct");
+    Assert.ok(tree.controller.isCommandEnabled("placesCmd_show:info"),
+              "'placesCmd_show:info' on folder is enabled");
+
+    let promiseTitleResetNotification = PlacesTestUtils.waitForNotification(
+      "onItemChanged", (id, prop, anno, val) => prop == "title" && val == "folder");
+
+    await withBookmarksDialog(
+      true,
+      function openDialog() {
+        tree.controller.doCommand("placesCmd_show:info");
+      },
+      async function test(dialogWin) {
+        // Check that the dialog is not read-only.
+        Assert.ok(!dialogWin.gEditItemOverlay.readOnly, "Dialog should not be read-only");
+
+        // Check that name picker is not read only
+        let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
+        Assert.ok(!namepicker.readOnly, "Name field should not be read-only");
+        Assert.equal(namepicker.value, "folder", "Node title is correct");
+        let promiseTitleChangeNotification = PlacesTestUtils.waitForNotification(
+          "onItemChanged", (id, prop, anno, val) => prop == "title" && val == "newname");
+
+        fillBookmarkTextField("editBMPanel_namePicker", "newname", dialogWin);
+
+        await promiseTitleChangeNotification;
+
+        Assert.equal(namepicker.value, "newname", "The title field has been changed");
+        Assert.equal(tree.selectedNode.title, "newname", "The node has the correct title");
+
+        // Ensure that the edit is finished before we hit cancel.
+        await PlacesTestUtils.promiseAsyncUpdates();
+      }
+    );
+
+    await promiseTitleResetNotification;
+  });
+});
--- a/browser/components/places/tests/browser/browser_bookmarksProperties.js
+++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js
@@ -29,18 +29,18 @@ const ACTION_ADD = 1;
 const TYPE_FOLDER = 0;
 const TYPE_BOOKMARK = 1;
 
 const TEST_URL = "http://www.example.com/";
 
 const DIALOG_URL = "chrome://browser/content/places/bookmarkProperties.xul";
 const DIALOG_URL_MINIMAL_UI = "chrome://browser/content/places/bookmarkProperties2.xul";
 
-ChromeUtils.import("resource:///modules/RecentWindow.jsm");
-var win = RecentWindow.getMostRecentBrowserWindow();
+ChromeUtils.import("resource:///modules/BrowserWindowTracker.jsm");
+var win = BrowserWindowTracker.getTopWindow();
 
 function add_bookmark(url) {
   return PlacesUtils.bookmarks.insert({
     parentGuid: PlacesUtils.bookmarks.unfiledGuid,
     url,
     title: `bookmark/${url}`
   });
 }
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -155,17 +155,17 @@
             <image class="help-icon"/>
               <label class="help-label" flex="1" data-l10n-id="help-button-label"></label>
           </hbox>
         </label>
       </hbox>
     </vbox>
 
     <keyset>
-      <key data-l10n-id="focus-search" modifiers="accel" id="focusSearch1" oncommand="gSearchResultsPane.searchInput.focus();"/>
+      <key data-l10n-id="focus-search" key="" modifiers="accel" id="focusSearch1" oncommand="gSearchResultsPane.searchInput.focus();"/>
     </keyset>
 
     <vbox class="main-content" flex="1" align="start">
       <vbox class="pane-container">
         <hbox class="sticky-container" pack="end" align="top">
           <hbox id="policies-container" align="stretch" flex="1" hidden="true">
             <hbox align="top">
               <image class="info-icon"></image>
--- a/browser/components/preferences/in-content/tests/browser_fluent.js
+++ b/browser/components/preferences/in-content/tests/browser_fluent.js
@@ -19,30 +19,26 @@ add_task(async function() {
   }
 
   await Promise.all([
     openPreferencesViaOpenPreferencesAPI("general", {leaveOpen: true}),
     whenMainPaneLoadedFinished(),
   ]);
 
   let doc = gBrowser.contentDocument;
-  let win = gBrowser.contentWindow;
   await doc.l10n.ready;
 
-  let processCountPref = win.Preferences.get("dom.ipc.processCount");
-  let defaultProcessCount = processCountPref.defaultValue;
-
   let [ msg ] = await doc.l10n.formatMessages([
-    ["performance-default-content-process-count", { num: defaultProcessCount }]
+    ["category-general"],
   ]);
 
   let elem = doc.querySelector(
-    `#contentProcessCount > menupopup > menuitem[value="${defaultProcessCount}"]`);
+    `#category-general`);
 
   Assert.deepEqual(msg, {
     value: null,
     attributes: [
-      {name: "label", value: elem.getAttribute("label")}
+      {name: "tooltiptext", value: elem.getAttribute("tooltiptext")}
     ]
   });
 
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/components/preferences/in-content/tests/siteData/head.js
+++ b/browser/components/preferences/in-content/tests/siteData/head.js
@@ -10,17 +10,17 @@ const TEST_OFFLINE_HOST = "example.org";
 const TEST_OFFLINE_ORIGIN = "https://" + TEST_OFFLINE_HOST;
 const TEST_OFFLINE_URL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", TEST_OFFLINE_ORIGIN) + "/offline/offline.html";
 const TEST_SERVICE_WORKER_URL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", TEST_OFFLINE_ORIGIN) + "/service_worker_test.html";
 
 const REMOVE_DIALOG_URL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
 
 const { DownloadUtils } = ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm", {});
 const { SiteDataManager } = ChromeUtils.import("resource:///modules/SiteDataManager.jsm", {});
-const { OfflineAppCacheHelper } = ChromeUtils.import("resource:///modules/offlineAppCache.jsm", {});
+const { OfflineAppCacheHelper } = ChromeUtils.import("resource://gre/modules/offlineAppCache.jsm", {});
 
 ChromeUtils.defineModuleGetter(this, "SiteDataTestUtils",
                                "resource://testing-common/SiteDataTestUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager", "@mozilla.org/serviceworkers/manager;1", "nsIServiceWorkerManager");
 
 function promiseSiteDataManagerSitesUpdated() {
   return TestUtils.topicObserved("sitedatamanager:sites-updated", () => true);
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -4,19 +4,25 @@
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["SessionStore"];
 
 // Current version of the format used by Session Restore.
 const FORMAT_VERSION = 1;
 
+const TAB_CUSTOM_VALUES = new WeakMap();
+const TAB_LAZY_STATES = new WeakMap();
 const TAB_STATE_NEEDS_RESTORE = 1;
 const TAB_STATE_RESTORING = 2;
 const TAB_STATE_WILL_RESTORE = 3;
+const TAB_STATE_FOR_BROWSER = new WeakMap();
+const WINDOW_RESTORE_IDS = new WeakMap();
+const WINDOW_RESTORE_ZINDICES = new WeakMap();
+const WINDOW_SHOWING_PROMISES = new Map();
 
 // A new window has just been restored. At this stage, tabs are generally
 // not restored.
 const NOTIFY_SINGLE_WINDOW_RESTORED = "sessionstore-single-window-restored";
 const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
 const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
 const NOTIFY_LAST_SESSION_CLEARED = "sessionstore-last-session-cleared";
 const NOTIFY_RESTORING_ON_STARTUP = "sessionstore-restoring-on-startup";
@@ -160,21 +166,21 @@ XPCOMUtils.defineLazyServiceGetters(this
   gSessionStartup: ["@mozilla.org/browser/sessionstartup;1", "nsISessionStartup"],
   gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
   Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
 });
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.jsm",
   GlobalState: "resource:///modules/sessionstore/GlobalState.jsm",
   PrivacyFilter: "resource:///modules/sessionstore/PrivacyFilter.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
-  RecentWindow: "resource:///modules/RecentWindow.jsm",
   RunState: "resource:///modules/sessionstore/RunState.jsm",
   SessionCookies: "resource:///modules/sessionstore/SessionCookies.jsm",
   SessionFile: "resource:///modules/sessionstore/SessionFile.jsm",
   SessionSaver: "resource:///modules/sessionstore/SessionSaver.jsm",
   TabAttributes: "resource:///modules/sessionstore/TabAttributes.jsm",
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
   TabState: "resource:///modules/sessionstore/TabState.jsm",
   TabStateCache: "resource:///modules/sessionstore/TabStateCache.jsm",
@@ -248,16 +254,20 @@ var SessionStore = {
   getTabState: function ss_getTabState(aTab) {
     return SessionStoreInternal.getTabState(aTab);
   },
 
   setTabState: function ss_setTabState(aTab, aState) {
     SessionStoreInternal.setTabState(aTab, aState);
   },
 
+  getInternalObjectState(obj) {
+    return SessionStoreInternal.getInternalObjectState(obj);
+  },
+
   duplicateTab: function ss_duplicateTab(aWindow, aTab, aDelta = 0, aRestoreImmediately = true) {
     return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta, aRestoreImmediately);
   },
 
   getClosedTabCount: function ss_getClosedTabCount(aWindow) {
     return SessionStoreInternal.getClosedTabCount(aWindow);
   },
 
@@ -678,23 +688,16 @@ var SessionStoreInternal = {
               state.windows[0].tabs[0].entries[0].url = "about:sessionrestore";
               state.windows[0].tabs[0].entries[0].triggeringPrincipal_base64 = Utils.SERIALIZED_SYSTEMPRINCIPAL;
             }
           }
 
           // Update the session start time using the restored session state.
           this._updateSessionStartTime(state);
 
-          // make sure that at least the first window doesn't have anything hidden
-          delete state.windows[0].hidden;
-          // Since nothing is hidden in the first window, it cannot be a popup
-          delete state.windows[0].isPopup;
-          // We don't want to minimize and then open a window at startup.
-          if (state.windows[0].sizemode == "minimized")
-            state.windows[0].sizemode = "normal";
           // clear any lastSessionWindowID attributes since those don't matter
           // during normal restore
           state.windows.forEach(function(aWindow) {
             delete aWindow.__lastSessionWindowID;
           });
         }
       } catch (ex) { debug("The session file is invalid: " + ex); }
     }
@@ -910,17 +913,17 @@ var SessionStoreInternal = {
               // longer consider its data interesting enough to keep around.
               this.removeClosedTabData(closedTabs, index);
             }
           }
         }
         break;
       case "SessionStore:restoreHistoryComplete": {
         // Notify the tabbrowser that the tab chrome has been restored.
-        let tabData = TabState.collect(tab);
+        let tabData = TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
 
         // wall-paper fix for bug 439675: make sure that the URL to be loaded
         // is always visible in the address bar if no other value is present
         let activePageData = tabData.entries[tabData.index - 1] || null;
         let uri = activePageData ? activePageData.url || null : null;
         // NB: we won't set initial URIs (about:home, about:newtab, etc.) here
         // because their load will not normally trigger a location bar clearing
         // when they finish loading (to avoid race conditions where we then
@@ -938,30 +941,30 @@ var SessionStoreInternal = {
         this.updateTabLabelAndIcon(tab, tabData);
 
         let event = win.document.createEvent("Events");
         event.initEvent("SSTabRestoring", true, false);
         tab.dispatchEvent(event);
         break;
       }
       case "SessionStore:restoreTabContentStarted":
-        if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+        if (TAB_STATE_FOR_BROWSER.get(browser) == TAB_STATE_NEEDS_RESTORE) {
           // If a load not initiated by sessionstore was started in a
           // previously pending tab. Mark the tab as no longer pending.
           this.markTabAsRestoring(tab);
         } else if (data.reason != RESTORE_TAB_CONTENT_REASON.NAVIGATE_AND_RESTORE) {
           // If the user was typing into the URL bar when we crashed, but hadn't hit
           // enter yet, then we just need to write that value to the URL bar without
           // loading anything. This must happen after the load, as the load will clear
           // userTypedValue.
           //
           // Note that we only want to do that if we're restoring state for reasons
           // _other_ than a navigateAndRestore remoteness-flip, as such a flip
           // implies that the user was navigating.
-          let tabData = TabState.collect(tab);
+          let tabData = TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
           if (tabData.userTypedValue && !tabData.userTypedClear && !browser.userTypedValue) {
             browser.userTypedValue = tabData.userTypedValue;
             win.URLBarSetURI();
           }
 
           // Remove state we don't need any longer.
           TabStateCache.update(browser, {
             userTypedValue: null, userTypedClear: null
@@ -1179,36 +1182,33 @@ var SessionStoreInternal = {
       } else {
         // Nothing to restore, notify observers things are complete.
         Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED);
         Services.obs.notifyObservers(null, "sessionstore-one-or-no-tab-restored");
         this._deferredAllWindowsRestored.resolve();
       }
     // this window was opened by _openWindowWithState
     } else if (!this._isWindowLoaded(aWindow)) {
-      let state = this._statesToRestore[aWindow.__SS_restoreID];
-      let options = {overwriteTabs: true, isFollowUp: state.windows.length == 1};
-      this.restoreWindow(aWindow, state.windows[0], options);
+      // We want to restore windows after all windows have opened (since bug
+      // 1034036), so bail out here.
+      return;
     // The user opened another, non-private window after starting up with
     // a single private one. Let's restore the session we actually wanted to
     // restore at startup.
-    } else if (this._deferredInitialState && !isPrivateWindow &&
-             aWindow.toolbar.visible) {
-
+    } else if (this._deferredInitialState && !isPrivateWindow && aWindow.toolbar.visible) {
       // global data must be restored before restoreWindow is called so that
       // it happens before observers are notified
       this._globalState.setFromState(this._deferredInitialState);
 
       this._restoreCount = this._deferredInitialState.windows ?
         this._deferredInitialState.windows.length : 0;
       this.restoreWindows(aWindow, this._deferredInitialState, {firstWindow: true});
       this._deferredInitialState = null;
     } else if (this._restoreLastWindow && aWindow.toolbar.visible &&
-             this._closedWindows.length && !isPrivateWindow) {
-
+               this._closedWindows.length && !isPrivateWindow) {
       // default to the most-recently closed window
       // don't use popup windows
       let closedWindowState = null;
       let closedWindowIndex;
       for (let i = 0; i < this._closedWindows.length; i++) {
         // Take the first non-popup, point our object at it, and break out.
         if (!this._closedWindows[i].isPopup) {
           closedWindowState = this._closedWindows[i];
@@ -1273,16 +1273,24 @@ var SessionStoreInternal = {
    * Called right before a new browser window is shown.
    * @param aWindow
    *        Window reference
    */
   onBeforeBrowserWindowShown(aWindow) {
     // Register the window.
     this.onLoad(aWindow);
 
+    // Some are waiting for this window to be shown, which is now, so let's resolve
+    // the deferred operation.
+    let deferred = WINDOW_SHOWING_PROMISES.get(aWindow);
+    if (deferred) {
+      deferred.resolve(aWindow);
+      WINDOW_SHOWING_PROMISES.delete(aWindow);
+    }
+
     // Just call initializeWindow() directly if we're initialized already.
     if (this._sessionInitialized) {
       this.initializeWindow(aWindow);
       return;
     }
 
     // The very first window that is opened creates a promise that is then
     // re-used by all subsequent windows. The promise will be used to tell
@@ -1347,19 +1355,20 @@ var SessionStoreInternal = {
     let completionPromise = Promise.resolve();
     // this window was about to be restored - conserve its original data, if any
     let isFullyLoaded = this._isWindowLoaded(aWindow);
     if (!isFullyLoaded) {
       if (!aWindow.__SSi) {
         aWindow.__SSi = this._generateWindowID();
       }
 
-      this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID];
-      delete this._statesToRestore[aWindow.__SS_restoreID];
-      delete aWindow.__SS_restoreID;
+      let restoreID = WINDOW_RESTORE_IDS.get(aWindow);
+      this._windows[aWindow.__SSi] = this._statesToRestore[restoreID].windows[0];
+      delete this._statesToRestore[restoreID];
+      WINDOW_RESTORE_IDS.delete(aWindow);
     }
 
     // ignore windows not tracked by SessionStore
     if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
       return completionPromise;
     }
 
     // notify that the session store will stop tracking this window so that
@@ -1573,20 +1582,22 @@ var SessionStoreInternal = {
       }
     }
   },
 
   /**
    * On quit application granted
    */
   onQuitApplicationGranted: function ssi_onQuitApplicationGranted(syncShutdown = false) {
-    // Collect an initial snapshot of window data before we do the flush
-    this._forEachBrowserWindow((win) => {
-      this._collectWindowData(win);
-    });
+    // Collect an initial snapshot of window data before we do the flush.
+    let index = 0;
+    for (let window of this._browserWindows) {
+      this._collectWindowData(window);
+      this._windows[window.__SSi].zIndex = ++index;
+    }
 
     // Now add an AsyncShutdown blocker that'll spin the event loop
     // until the windows have all been flushed.
 
     // This progress object will track the state of async window flushing
     // and will help us debug things that go wrong with our AsyncShutdown
     // blocker.
     let progress = { total: -1, current: -1 };
@@ -1661,44 +1672,44 @@ var SessionStoreInternal = {
    *
    * @return Promise
    */
   async flushAllWindowsAsync(progress = {}) {
     let windowPromises = new Map();
     // We collect flush promises and close each window immediately so that
     // the user can't start changing any window state while we're waiting
     // for the flushes to finish.
-    this._forEachBrowserWindow((win) => {
-      windowPromises.set(win, TabStateFlusher.flushWindow(win));
+    for (let window of this._browserWindows) {
+      windowPromises.set(window, TabStateFlusher.flushWindow(window));
 
       // We have to wait for these messages to come up from
       // each window and each browser. In the meantime, hide
       // the windows to improve perceived shutdown speed.
-      let baseWin = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDocShell)
-                       .QueryInterface(Ci.nsIDocShellTreeItem)
-                       .treeOwner
-                       .QueryInterface(Ci.nsIBaseWindow);
+      let baseWin = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDocShell)
+                          .QueryInterface(Ci.nsIDocShellTreeItem)
+                          .treeOwner
+                          .QueryInterface(Ci.nsIBaseWindow);
       baseWin.visibility = false;
-    });
+    }
 
     progress.total = windowPromises.size;
     progress.current = 0;
 
     // We'll iterate through the Promise array, yielding each one, so as to
     // provide useful progress information to AsyncShutdown.
     for (let [win, promise] of windowPromises) {
       await promise;
       this._collectWindowData(win);
       progress.current++;
     }
 
-    // We must cache this because _getMostRecentBrowserWindow will always
+    // We must cache this because _getTopWindow will always
     // return null by the time quit-application occurs.
-    var activeWindow = this._getMostRecentBrowserWindow();
+    var activeWindow = this._getTopWindow();
     if (activeWindow)
       this.activeWindowSSiCache = activeWindow.__SSi || "";
     DirtyWindows.clear();
   },
 
   /**
    * On last browser window close
    */
@@ -1747,17 +1758,19 @@ var SessionStoreInternal = {
     // session data on disk as this notification fires after the
     // quit-application notification so the browser is about to exit.
     if (RunState.isQuitting)
       return;
     LastSession.clear();
 
     let openWindows = {};
     // Collect open windows.
-    this._forEachBrowserWindow(({__SSi: id}) => openWindows[id] = true);
+    for (let window of this._browserWindows) {
+      openWindows[window.__SSi] = true;
+    }
 
     // also clear all data about closed tabs and windows
     for (let ix in this._windows) {
       if (ix in openWindows) {
         if (this._windows[ix]._closedTabs.length) {
           this._windows[ix]._closedTabs = [];
           this._closedObjectsChanged = true;
         }
@@ -1766,17 +1779,17 @@ var SessionStoreInternal = {
       }
     }
     // also clear all data about closed windows
     if (this._closedWindows.length) {
       this._closedWindows = [];
       this._closedObjectsChanged = true;
     }
     // give the tabbrowsers a chance to clear their histories first
-    var win = this._getMostRecentBrowserWindow();
+    var win = this._getTopWindow();
     if (win) {
       win.setTimeout(() => SessionSaver.run(), 0);
     } else if (RunState.isRunning) {
       SessionSaver.run();
     }
 
     this._clearRestoringWindows();
     this._saveableClosedWindowData = new WeakSet();
@@ -1893,23 +1906,23 @@ var SessionStoreInternal = {
     browser.addEventListener("SwapDocShells", this);
     browser.addEventListener("oop-browser-crashed", this);
 
     if (browser.frameLoader) {
       this._lastKnownFrameLoader.set(browser.permanentKey, browser.frameLoader);
     }
 
     // Only restore if browser has been lazy.
-    if (aTab.__SS_lazyData && !browser.__SS_restoreState && TabStateCache.get(browser)) {
-      let tabState = TabState.clone(aTab);
+    if (TAB_LAZY_STATES.has(aTab) && !TAB_STATE_FOR_BROWSER.has(browser) && TabStateCache.get(browser)) {
+      let tabState = TabState.clone(aTab, TAB_CUSTOM_VALUES.get(aTab));
       this.restoreTab(aTab, tabState);
     }
 
     // The browser has been inserted now, so lazy data is no longer relevant.
-    delete aTab.__SS_lazyData;
+    TAB_LAZY_STATES.delete(aTab);
   },
 
   /**
    * remove listeners for a tab
    * @param aWindow
    *        Window reference
    * @param aTab
    *        Tab reference
@@ -1939,17 +1952,17 @@ var SessionStoreInternal = {
     aTab.dispatchEvent(event);
 
     // don't update our internal state if we don't have to
     if (this._max_tabs_undo == 0) {
       return;
     }
 
     // Get the latest data for this tab (generally, from the cache)
-    let tabState = TabState.collect(aTab);
+    let tabState = TabState.collect(aTab, TAB_CUSTOM_VALUES.get(aTab));
 
     // Don't save private tabs
     let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
     if (!isPrivateWindow && tabState.isPrivate) {
       return;
     }
 
     // Store closed-tab data for undo.
@@ -2002,39 +2015,39 @@ var SessionStoreInternal = {
     this.cleanUpRemovedBrowser(aTab);
 
     aTab.setAttribute("pending", "true");
 
     this._lastKnownFrameLoader.delete(browser.permanentKey);
     this._crashedBrowsers.delete(browser.permanentKey);
     aTab.removeAttribute("crashed");
 
-    aTab.__SS_lazyData = {
+    TAB_LAZY_STATES.set(aTab, {
       url: browser.currentURI.spec,
       title: aTab.label,
       userTypedValue: browser.userTypedValue || "",
       userTypedClear: browser.userTypedClear || 0
-    };
+    });
   },
 
   /**
    * When a tab is removed or suspended, remove listeners and reset restoring state.
    * @param aBrowser
    *        Browser reference
    */
   cleanUpRemovedBrowser(aTab) {
     let browser = aTab.linkedBrowser;
 
     browser.removeEventListener("SwapDocShells", this);
     browser.removeEventListener("oop-browser-crashed", this);
 
     // If this tab was in the middle of restoring or still needs to be restored,
     // we need to reset that state. If the tab was restoring, we will attempt to
     // restore the next tab.
-    let previousState = browser.__SS_restoreState;
+    let previousState = TAB_STATE_FOR_BROWSER.get(browser);
     if (previousState) {
       this._resetTabRestoringState(aTab);
       if (previousState == TAB_STATE_RESTORING)
         this.restoreNextTab();
     }
   },
 
   /**
@@ -2109,18 +2122,18 @@ var SessionStoreInternal = {
    */
   onTabSelect: function ssi_onTabSelect(aWindow) {
     if (RunState.isRunning) {
       this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
 
       let tab = aWindow.gBrowser.selectedTab;
       let browser = tab.linkedBrowser;
 
-      if (browser.__SS_restoreState &&
-          browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+      if (TAB_STATE_FOR_BROWSER.get(browser) == TAB_STATE_NEEDS_RESTORE) {
+        // If BROWSER_STATE is still available for the browser and it is
         // If __SS_restoreState is still on the browser and it is
         // TAB_STATE_NEEDS_RESTORE, then then we haven't restored
         // this tab yet.
         //
         // It's possible that this tab was recently revived, and that
         // we've deferred showing the tab crashed page for it (if the
         // tab crashed in the background). If so, we need to re-enter
         // the crashed state, since we'll be showing the tab crashed
@@ -2131,35 +2144,33 @@ var SessionStoreInternal = {
           this.restoreTabContent(tab);
         }
       }
     }
   },
 
   onTabShow: function ssi_onTabShow(aWindow, aTab) {
     // If the tab hasn't been restored yet, move it into the right bucket
-    if (aTab.linkedBrowser.__SS_restoreState &&
-        aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+    if (TAB_STATE_FOR_BROWSER.get(aTab.linkedBrowser) == TAB_STATE_NEEDS_RESTORE) {
       TabRestoreQueue.hiddenToVisible(aTab);
 
       // let's kick off tab restoration again to ensure this tab gets restored
       // with "restore_hidden_tabs" == false (now that it has become visible)
       this.restoreNextTab();
     }
 
     // Default delay of 2 seconds gives enough time to catch multiple TabShow
     // events. This used to be due to changing groups in 'tab groups'. We
     // might be able to get rid of this now?
     this.saveStateDelayed(aWindow);
   },
 
   onTabHide: function ssi_onTabHide(aWindow, aTab) {
     // If the tab hasn't been restored yet, move it into the right bucket
-    if (aTab.linkedBrowser.__SS_restoreState &&
-        aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+    if (TAB_STATE_FOR_BROWSER.get(aTab.linkedBrowser) == TAB_STATE_NEEDS_RESTORE) {
       TabRestoreQueue.visibleToHidden(aTab);
     }
 
     // Default delay of 2 seconds gives enough time to catch multiple TabHide
     // events. This used to be due to changing groups in 'tab groups'. We
     // might be able to get rid of this now?
     this.saveStateDelayed(aWindow);
   },
@@ -2191,17 +2202,17 @@ var SessionStoreInternal = {
     this._crashedBrowsers.add(browser.permanentKey);
 
     let win = browser.ownerGlobal;
 
     // If we hadn't yet restored, or were still in the midst of
     // restoring this browser at the time of the crash, we need
     // to reset its state so that we can try to restore it again
     // when the user revives the tab from the crash.
-    if (browser.__SS_restoreState) {
+    if (TAB_STATE_FOR_BROWSER.has(browser)) {
       let tab = win.gBrowser.getTabForBrowser(browser);
       this._resetLocalTabRestoringState(tab);
     }
   },
 
   // Clean up data that has been closed a long time ago.
   // Do not reschedule a save. This will wait for the next regular
   // save.
@@ -2265,30 +2276,30 @@ var SessionStoreInternal = {
       throw Components.Exception("No windows", Cr.NS_ERROR_INVALID_ARG);
     }
 
     this._browserSetState = true;
 
     // Make sure the priority queue is emptied out
     this._resetRestoringState();
 
-    var window = this._getMostRecentBrowserWindow();
+    var window = this._getTopWindow();
     if (!window) {
       this._restoreCount = 1;
       this._openWindowWithState(state);
       return;
     }
 
     // close all other browser windows
-    this._forEachBrowserWindow(function(aWindow) {
-      if (aWindow != window) {
-        aWindow.close();
-        this.onClose(aWindow);
+    for (let otherWin of this._browserWindows) {
+      if (otherWin != window) {
+        otherWin.close();
+        this.onClose(otherWin);
       }
-    });
+    }
 
     // make sure closed window data isn't kept
     if (this._closedWindows.length) {
       this._closedWindows = [];
       this._closedObjectsChanged = true;
     }
 
     // determine how many windows are meant to be restored
@@ -2335,17 +2346,17 @@ var SessionStoreInternal = {
   getTabState: function ssi_getTabState(aTab) {
     if (!aTab || !aTab.ownerGlobal) {
       throw Components.Exception("Need a valid tab", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aTab.ownerGlobal.__SSi) {
       throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    let tabState = TabState.collect(aTab);
+    let tabState = TabState.collect(aTab, TAB_CUSTOM_VALUES.get(aTab));
 
     return JSON.stringify(tabState);
   },
 
   setTabState(aTab, aState) {
     // Remove the tab state from the cache.
     // Note that we cannot simply replace the contents of the cache
     // as |aState| can be an incomplete state that will be completed
@@ -2361,26 +2372,33 @@ var SessionStoreInternal = {
       throw Components.Exception("Invalid state object: no entries", Cr.NS_ERROR_INVALID_ARG);
     }
 
     let window = aTab.ownerGlobal;
     if (!window || !("__SSi" in window)) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    if (aTab.linkedBrowser.__SS_restoreState) {
+    if (TAB_STATE_FOR_BROWSER.has(aTab.linkedBrowser)) {
       this._resetTabRestoringState(aTab);
     }
 
     this.restoreTab(aTab, tabState);
 
     // Notify of changes to closed objects.
     this._notifyOfClosedObjectsChange();
   },
 
+  getInternalObjectState(obj) {
+    if (obj.__SSi) {
+      return this._windows[obj.__SSi];
+    }
+    return obj.loadURI ? TAB_STATE_FOR_BROWSER.get(obj) : TAB_CUSTOM_VALUES.get(obj);
+  },
+
   duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0, aRestoreImmediately = true) {
     if (!aTab || !aTab.ownerGlobal) {
       throw Components.Exception("Need a valid tab", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aTab.ownerGlobal.__SSi) {
       throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aWindow.gBrowser) {
@@ -2393,17 +2411,17 @@ var SessionStoreInternal = {
       aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab, userContextId}) :
       aWindow.gBrowser.addTab(null, {userContextId});
 
     // Start the throbber to pretend we're doing something while actually
     // waiting for data from the frame script.
     newTab.setAttribute("busy", "true");
 
     // Collect state before flushing.
-    let tabState = TabState.clone(aTab);
+    let tabState = TabState.collect(aTab, TAB_CUSTOM_VALUES.get(aTab));
 
     // Flush to get the latest tab state to duplicate.
     let browser = aTab.linkedBrowser;
     TabStateFlusher.flush(browser).then(() => {
       // The new tab might have been closed in the meantime.
       if (newTab.closing || !newTab.linkedBrowser) {
         return;
       }
@@ -2530,16 +2548,18 @@ var SessionStoreInternal = {
     }
 
     // reopen the window
     let state = { windows: this._removeClosedWindow(aIndex) };
     delete state.windows[0].closedAt; // Window is now open.
 
     let window = this._openWindowWithState(state);
     this.windowToFocus = window;
+    WINDOW_SHOWING_PROMISES.get(window).promise.then(win =>
+      this.restoreWindows(win, state, {overwriteTabs: true}));
 
     // Notify of changes to closed objects.
     this._notifyOfClosedObjectsChange();
 
     return window;
   },
 
   forgetClosedWindow: function ssi_forgetClosedWindow(aIndex) {
@@ -2590,52 +2610,53 @@ var SessionStoreInternal = {
   deleteWindowValue: function ssi_deleteWindowValue(aWindow, aKey) {
     if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
         this._windows[aWindow.__SSi].extData[aKey])
       delete this._windows[aWindow.__SSi].extData[aKey];
     this.saveStateDelayed(aWindow);
   },
 
   getCustomTabValue(aTab, aKey) {
-    return (aTab.__SS_extdata || {})[aKey] || "";
+    return (TAB_CUSTOM_VALUES.get(aTab) || {})[aKey] || "";
   },
 
   setCustomTabValue(aTab, aKey, aStringValue) {
     if (typeof aStringValue != "string") {
       throw new TypeError("setCustomTabValue only accepts string values");
     }
 
     // If the tab hasn't been restored, then set the data there, otherwise we
     // could lose newly added data.
-    if (!aTab.__SS_extdata) {
-      aTab.__SS_extdata = {};
-    }
-
-    aTab.__SS_extdata[aKey] = aStringValue;
+    if (!TAB_CUSTOM_VALUES.has(aTab)) {
+      TAB_CUSTOM_VALUES.set(aTab, {});
+    }
+
+    TAB_CUSTOM_VALUES.get(aTab)[aKey] = aStringValue;
     this.saveStateDelayed(aTab.ownerGlobal);
   },
 
   deleteCustomTabValue(aTab, aKey) {
-    if (aTab.__SS_extdata && aKey in aTab.__SS_extdata) {
-      delete aTab.__SS_extdata[aKey];
+    let state = TAB_CUSTOM_VALUES.get(aTab);
+    if (state && aKey in state) {
+      delete state[aKey];
       this.saveStateDelayed(aTab.ownerGlobal);
     }
   },
 
   /**
    * Retrieves data specific to lazy-browser tabs.  If tab is not lazy,
    * will return undefined.
    *
    * @param aTab (xul:tab)
    *        The tabbrowser-tab the data is for.
    * @param aKey (string)
    *        The key which maps to the desired data.
    */
   getLazyTabValue(aTab, aKey) {
-    return (aTab.__SS_lazyData || {})[aKey];
+    return (TAB_LAZY_STATES.get(aTab) || {})[aKey];
   },
 
   getGlobalValue: function ssi_getGlobalValue(aKey) {
     return this._globalState.get(aKey);
   },
 
   setGlobalValue: function ssi_setGlobalValue(aKey, aStringValue) {
     if (typeof aStringValue != "string") {
@@ -2707,17 +2728,17 @@ var SessionStoreInternal = {
     if (tab.hasAttribute("customizemode")) {
       return;
     }
 
     let browser = tab.linkedBrowser;
     let win = browser.ownerGlobal;
 
     if (!tabData) {
-      tabData = TabState.collect(tab);
+      tabData = TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
       if (!tabData) {
         throw new Error("tabData not found for given tab");
       }
     }
 
     let activePageData = tabData.entries[tabData.index - 1] || null;
 
     // If the page has a title, set it.
@@ -2778,20 +2799,20 @@ var SessionStoreInternal = {
     if (!this.canRestoreLastSession) {
       throw Components.Exception("Last session can not be restored");
     }
 
     Services.obs.notifyObservers(null, NOTIFY_INITIATING_MANUAL_RESTORE);
 
     // First collect each window with its id...
     let windows = {};
-    this._forEachBrowserWindow(function(aWindow) {
-      if (aWindow.__SS_lastSessionWindowID)
-        windows[aWindow.__SS_lastSessionWindowID] = aWindow;
-    });
+    for (let window of this._browserWindows) {
+      if (window.__SS_lastSessionWindowID)
+        windows[window.__SS_lastSessionWindowID] = window;
+    }
 
     let lastSessionState = LastSession.getState();
 
     // This shouldn't ever be the case...
     if (!lastSessionState.windows.length) {
       throw Components.Exception("lastSessionState has no windows", Cr.NS_ERROR_UNEXPECTED);
     }
 
@@ -2799,24 +2820,27 @@ var SessionStoreInternal = {
     // notification when we're done. We want to send "sessionstore-browser-state-restored".
     this._restoreCount = lastSessionState.windows.length;
     this._browserSetState = true;
 
     // We want to re-use the last opened window instead of opening a new one in
     // the case where it's "empty" and not associated with a window in the session.
     // We will do more processing via _prepWindowToRestoreInto if we need to use
     // the lastWindow.
-    let lastWindow = this._getMostRecentBrowserWindow();
+    let lastWindow = this._getTopWindow();
     let canUseLastWindow = lastWindow &&
                            !lastWindow.__SS_lastSessionWindowID;
 
     // global data must be restored before restoreWindow is called so that
     // it happens before observers are notified
     this._globalState.setFromState(lastSessionState);
 
+    let openWindows = [];
+    let windowsToOpen = [];
+
     // Restore session cookies.
     SessionCookies.restore(lastSessionState.cookies || []);
 
     // Restore into windows or open new ones as needed.
     for (let i = 0; i < lastSessionState.windows.length; i++) {
       let winState = lastSessionState.windows[i];
       let lastSessionWindowID = winState.__lastSessionWindowID;
       // delete lastSessionWindowID so we don't add that to the window again
@@ -2838,27 +2862,34 @@ var SessionStoreInternal = {
         // Since we're not overwriting existing tabs, we want to merge _closedTabs,
         // putting existing ones first. Then make sure we're respecting the max pref.
         if (winState._closedTabs && winState._closedTabs.length) {
           let curWinState = this._windows[windowToUse.__SSi];
           curWinState._closedTabs = curWinState._closedTabs.concat(winState._closedTabs);
           curWinState._closedTabs.splice(this._max_tabs_undo, curWinState._closedTabs.length);
         }
 
-        // Restore into that window - pretend it's a followup since we'll already
-        // have a focused window.
         // XXXzpao This is going to merge extData together (taking what was in
         //        winState over what is in the window already.
-        let options = {overwriteTabs: canOverwriteTabs, isFollowUp: true};
-        this.restoreWindow(windowToUse, winState, options);
+        // We don't restore window right away, just store its data.
+        // Later, these windows will be restored with newly opened windows.
+        this._updateWindowRestoreState(windowToUse, {
+          windows: [winState],
+          options: {overwriteTabs: canOverwriteTabs}
+        });
+        openWindows.push(windowToUse);
       } else {
-        this._openWindowWithState({ windows: [winState] });
+        windowsToOpen.push(winState);
       }
     }
 
+    // Actually restore windows in reversed z-order.
+    this._openWindows({windows: windowsToOpen}).then(openedWindows =>
+      this._restoreWindowsInReversedZOrder(openWindows.concat(openedWindows)));
+
     // Merge closed windows from this session with ones from last session
     if (lastSessionState._closedWindows) {
       this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
       this._capClosedWindows();
       this._closedObjectsChanged = true;
     }
 
     DevToolsShim.restoreDevToolsSession(lastSessionState);
@@ -2903,17 +2934,17 @@ var SessionStoreInternal = {
 
     // We put the browser at about:blank in case the user is
     // restoring tabs on demand. This way, the user won't see
     // a flash of the about:tabcrashed page after selecting
     // the revived tab.
     aTab.removeAttribute("crashed");
     browser.loadURI("about:blank");
 
-    let data = TabState.collect(aTab);
+    let data = TabState.collect(aTab, TAB_CUSTOM_VALUES.get(aTab));
     this.restoreTab(aTab, data, {
       forceOnDemand: true,
     });
   },
 
   /**
    * Revive all crashed tabs and reset the crashed tabs count to 0.
    */
@@ -2984,17 +3015,17 @@ var SessionStoreInternal = {
 
       let refreshedWindow = tab.ownerGlobal;
 
       // The tab or its window might be gone.
       if (!refreshedWindow || !refreshedWindow.__SSi || refreshedWindow.closed) {
         return;
       }
 
-      let tabState = TabState.clone(tab);
+      let tabState = TabState.clone(tab, TAB_CUSTOM_VALUES.get(tab));
       let options = {
         restoreImmediately: true,
         // We want to make sure that this information is passed to restoreTab
         // whether or not a historyIndex is passed in. Thus, we extract it from
         // the loadArguments.
         newFrameloader: recentLoadArguments.newFrameloader,
         remoteType: recentLoadArguments.remoteType,
         // Make sure that SessionStore knows that this restoration is due
@@ -3005,25 +3036,25 @@ var SessionStoreInternal = {
       if (historyIndex >= 0) {
         tabState.index = historyIndex + 1;
         tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
       } else {
         options.loadArguments = recentLoadArguments;
       }
 
       // Need to reset restoring tabs.
-      if (tab.linkedBrowser.__SS_restoreState) {
+      if (TAB_STATE_FOR_BROWSER.has(tab.linkedBrowser)) {
         this._resetLocalTabRestoringState(tab);
       }
 
       // Restore the state into the tab.
       this.restoreTab(tab, tabState, options);
     });
 
-    tab.linkedBrowser.__SS_restoreState = TAB_STATE_WILL_RESTORE;
+    TAB_STATE_FOR_BROWSER.set(tab.linkedBrowser, TAB_STATE_WILL_RESTORE);
 
     // Notify of changes to closed objects.
     this._notifyOfClosedObjectsChange();
   },
 
   /**
    * Retrieves the latest session history information for a tab. The cached data
    * is returned immediately, but a callback may be provided that supplies
@@ -3042,17 +3073,17 @@ var SessionStoreInternal = {
         if (sessionHistory) {
           updatedCallback(sessionHistory);
         }
       });
     }
 
     // Don't continue if the tab was closed before TabStateFlusher.flush resolves.
     if (tab.linkedBrowser) {
-      let tabState = TabState.collect(tab);
+      let tabState = TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
       return { index: tabState.index - 1, entries: tabState.entries };
     }
     return null;
   },
 
   /**
    * See if aWindow is usable for use when restoring a previous session via
    * restoreLastSession. If usable, prepare it for use.
@@ -3138,30 +3169,32 @@ var SessionStoreInternal = {
    *        Bool update all windows
    * @returns object
    */
   getCurrentState(aUpdateAll) {
     this._handleClosedWindows().then(() => {
       this._notifyOfClosedObjectsChange();
     });
 
-    var activeWindow = this._getMostRecentBrowserWindow();
+    var activeWindow = this._getTopWindow();
 
     TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
     if (RunState.isRunning) {
-      // update the data for all windows with activities since the last save operation
-      this._forEachBrowserWindow(function(aWindow) {
-        if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore
-          return;
-        if (aUpdateAll || DirtyWindows.has(aWindow) || aWindow == activeWindow) {
-          this._collectWindowData(aWindow);
+      // update the data for all windows with activities since the last save operation.
+      let index = 0;
+      for (let window of this._browserWindows) {
+        if (!this._isWindowLoaded(window)) // window data is still in _statesToRestore
+          continue;
+        if (aUpdateAll || DirtyWindows.has(window) || window == activeWindow) {
+          this._collectWindowData(window);
         } else { // always update the window features (whose change alone never triggers a save operation)
-          this._updateWindowFeatures(aWindow);
+          this._updateWindowFeatures(window);
         }
-      });
+        this._windows[window.__SSi].zIndex = ++index;
+      }
       DirtyWindows.clear();
     }
     TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
 
     // An array that at the end will hold all current window data.
     var total = [];
     // The ids of all windows contained in 'total' in the same order.
     var ids = [];
@@ -3254,17 +3287,17 @@ var SessionStoreInternal = {
   /**
    * serialize session data for a window
    * @param aWindow
    *        Window reference
    * @returns string
    */
   _getWindowState: function ssi_getWindowState(aWindow) {
     if (!this._isWindowLoaded(aWindow))
-      return this._statesToRestore[aWindow.__SS_restoreID];
+      return this._statesToRestore[WINDOW_RESTORE_IDS.get(aWindow)];
 
     if (RunState.isRunning) {
       this._collectWindowData(aWindow);
     }
 
     return { windows: [this._windows[aWindow.__SSi]] };
   },
 
@@ -3285,17 +3318,17 @@ var SessionStoreInternal = {
 
     let tabbrowser = aWindow.gBrowser;
     let tabs = tabbrowser.tabs;
     let winData = this._windows[aWindow.__SSi];
     let tabsData = winData.tabs = [];
 
     // update the internal state data for this window
     for (let tab of tabs) {
-      let tabData = TabState.collect(tab);
+      let tabData = TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
       tabMap.set(tab, tabData);
       tabsData.push(tabData);
     }
     winData.selected = tabbrowser.tabbox.selectedIndex + 1;
 
     this._updateWindowFeatures(aWindow);
 
     // Make sure we keep __SS_lastSessionWindowID around for cases like entering
@@ -3306,37 +3339,47 @@ var SessionStoreInternal = {
 
     DirtyWindows.remove(aWindow);
     return tabMap;
   },
 
   /* ........ Restoring Functionality .............. */
 
   /**
+   * Open windows with data
+   *
+   * @param root
+   *        Windows data
+   * @returns a promise resolved when all windows have been opened
+   */
+  _openWindows(root) {
+    for (let winData of root.windows) {
+      if (!winData || !winData.tabs || !winData.tabs[0])
+        continue;
+      this._openWindowWithState({ windows: [winData] });
+    }
+    return Promise.all([...WINDOW_SHOWING_PROMISES.values()].map(deferred => deferred.promise));
+  },
+
+  /**
    * restore features to a single window
    * @param aWindow
    *        Window reference to the window to use for restoration
    * @param winData
    *        JS object
    * @param aOptions
    *        {overwriteTabs: true} to overwrite existing tabs w/ new ones
-   *        {isFollowUp: true} if this is not the restoration of the 1st window
    *        {firstWindow: true} if this is the first non-private window we're
    *                            restoring in this session, that might open an
    *                            external link as well
    */
   restoreWindow: function ssi_restoreWindow(aWindow, winData, aOptions = {}) {
     let overwriteTabs = aOptions && aOptions.overwriteTabs;
-    let isFollowUp = aOptions && aOptions.isFollowUp;
     let firstWindow = aOptions && aOptions.firstWindow;
 
-    if (isFollowUp) {
-      this.windowToFocus = aWindow;
-    }
-
     // initialize window if necessary
     if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
       this.onLoad(aWindow);
 
     TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
 
     // We're not returning from this before we end up calling restoreTabs
     // for this window, so make sure we send the SSWindowStateBusy event.
@@ -3464,17 +3507,16 @@ var SessionStoreInternal = {
     // We want to correlate the window with data from the last session, so
     // assign another id if we have one. Otherwise clear so we don't do
     // anything with it.
     delete aWindow.__SS_lastSessionWindowID;
     if (winData.__lastSessionWindowID)
       aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
 
     if (overwriteTabs) {
-      this.restoreWindowFeatures(aWindow, winData);
       delete this._windows[aWindow.__SSi].extData;
     }
 
     // Restore cookies from legacy sessions, i.e. before bug 912717.
     SessionCookies.restore(winData.cookies || []);
 
     if (winData.extData) {
       if (!this._windows[aWindow.__SSi].extData) {
@@ -3544,51 +3586,82 @@ var SessionStoreInternal = {
    * Make a connection to a host when users hover mouse on a tab.
    * This will also set a flag in the tab to prevent us from speculatively
    * connecting a second time.
    *
    * @param tab
    *        a tab to speculatively connect on mouse hover.
    */
   speculativeConnectOnTabHover(tab) {
-    if (tab.__SS_lazyData && !tab.__SS_connectionPrepared) {
+    let tabState = TAB_LAZY_STATES.get(tab);
+    if (tabState && !tabState.connectionPrepared) {
       let url = this.getLazyTabValue(tab, "url");
       let prepared = this.prepareConnectionToHost(url);
       // This is used to test if a connection has been made beforehand.
       if (gDebuggingEnabled) {
         tab.__test_connection_prepared = prepared;
         tab.__test_connection_url = url;
       }
       // A flag indicate that we've prepared a connection for this tab and
       // if is called again, we shouldn't prepare another connection.
-      tab.__SS_connectionPrepared = true;
-    }
+      tabState.connectionPrepared = true;
+    }
+  },
+
+  /**
+   * This function will restore window features and then retore window data.
+   *
+   * @param windows
+   *        ordered array of windows to restore
+   */
+  _restoreWindowsFeaturesAndTabs(windows) {
+    // First, we restore window features, so that when users start interacting
+    // with a window, we don't steal the window focus.
+    for (let window of windows) {
+      let state = this._statesToRestore[WINDOW_RESTORE_IDS.get(window)];
+      this.restoreWindowFeatures(window, state.windows[0]);
+    }
+
+    // Then we restore data into windows.
+    for (let window of windows) {
+      let state = this._statesToRestore[WINDOW_RESTORE_IDS.get(window)];
+      this.restoreWindow(window, state.windows[0], state.options || {overwriteTabs: true});
+      WINDOW_RESTORE_ZINDICES.delete(window);
+    }
+  },
+
+  /**
+   * This function will restore window in reversed z-index, so that users will
+   * be presented with most recently used window first.
+   *
+   * @param windows
+   *        unordered array of windows to restore
+   */
+  _restoreWindowsInReversedZOrder(windows) {
+    windows.sort((a, b) =>
+      (WINDOW_RESTORE_ZINDICES.get(a) || 0) - (WINDOW_RESTORE_ZINDICES.get(b) || 0));
+
+    this.windowToFocus = windows[0];
+    this._restoreWindowsFeaturesAndTabs(windows);
   },
 
   /**
    * Restore multiple windows using the provided state.
    * @param aWindow
    *        Window reference to the first window to use for restoration.
    *        Additionally required windows will be opened.
    * @param aState
    *        JS object or JSON string
    * @param aOptions
    *        {overwriteTabs: true} to overwrite existing tabs w/ new ones
-   *        {isFollowUp: true} if this is not the restoration of the 1st window
    *        {firstWindow: true} if this is the first non-private window we're
    *                            restoring in this session, that might open an
    *                            external link as well
    */
   restoreWindows: function ssi_restoreWindows(aWindow, aState, aOptions = {}) {
-    let isFollowUp = aOptions && aOptions.isFollowUp;
-
-    if (isFollowUp) {
-      this.windowToFocus = aWindow;
-    }
-
     // initialize window if necessary
     if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
       this.onLoad(aWindow);
 
     let root;
     try {
       root = (typeof aState == "string") ? JSON.parse(aState) : aState;
     } catch (ex) { // invalid state object - don't restore anything
@@ -3604,34 +3677,31 @@ var SessionStoreInternal = {
     }
 
     // We're done here if there are no windows.
     if (!root.windows || !root.windows.length) {
       this._sendRestoreCompletedNotifications();
       return;
     }
 
-    if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
-      root.selectedWindow = 0;
-    }
-
-    // open new windows for all further window entries of a multi-window session
-    // (unless they don't contain any tab data)
-    let winData;
-    for (var w = 1; w < root.windows.length; w++) {
-      winData = root.windows[w];
-      if (winData && winData.tabs && winData.tabs[0]) {
-        var window = this._openWindowWithState({ windows: [winData] });
-        if (w == root.selectedWindow - 1) {
-          this.windowToFocus = window;
-        }
-      }
-    }
-
-    this.restoreWindow(aWindow, root.windows[0], aOptions);
+    let firstWindowData = root.windows.splice(0, 1);
+    // Store the restore state and restore option of the current window,
+    // so that the window can be restored in reversed z-order.
+    this._updateWindowRestoreState(aWindow, {windows: firstWindowData, options: aOptions});
+
+    // Begin the restoration: First open all windows in creation order. After all
+    // windows have opened, we restore states to windows in reversed z-order.
+    this._openWindows(root).then(windows => {
+      // We want to add current window to opened window, so that this window will be
+      // restored in reversed z-order. (We add the window to first position, in case
+      // no z-indices are found, that window will be restored first.)
+      windows.unshift(aWindow);
+
+      this._restoreWindowsInReversedZOrder(windows);
+    });
 
     DevToolsShim.restoreDevToolsSession(aState);
   },
 
   /**
    * Manage history restoration for a window
    * @param aWindow
    *        Window to restore the tabs into
@@ -3644,18 +3714,18 @@ var SessionStoreInternal = {
    *        indicates the first tab should be selected, and "0" indicates that
    *        the currently selected tab will not be changed.
    */
   restoreTabs(aWindow, aTabs, aTabData, aSelectTab) {
     var tabbrowser = aWindow.gBrowser;
 
     if (!this._isWindowLoaded(aWindow)) {
       // from now on, the data will come from the actual window
-      delete this._statesToRestore[aWindow.__SS_restoreID];
-      delete aWindow.__SS_restoreID;
+      delete this._statesToRestore[WINDOW_RESTORE_IDS.get(aWindow)];
+      WINDOW_RESTORE_IDS.delete(aWindow);
       delete this._windows[aWindow.__SSi]._restoring;
     }
 
     let numTabsToRestore = aTabs.length;
     let numTabsInWindow = tabbrowser.tabs.length;
     let tabsDataArray = this._windows[aWindow.__SSi].tabs;
 
     // Update the window state in case we shut down without being notified.
@@ -3689,17 +3759,17 @@ var SessionStoreInternal = {
       }
     }
   },
 
   // Restores the given tab state for a given tab.
   restoreTab(tab, tabData, options = {}) {
     let browser = tab.linkedBrowser;
 
-    if (browser.__SS_restoreState) {
+    if (TAB_STATE_FOR_BROWSER.has(browser)) {
       Cu.reportError("Must reset tab before calling restoreTab.");
       return;
     }
 
     let loadArguments = options.loadArguments;
     let window = tab.ownerGlobal;
     let tabbrowser = window.gBrowser;
     let forceOnDemand = options.forceOnDemand;
@@ -3758,19 +3828,19 @@ var SessionStoreInternal = {
       // Ensure that we persist tab attributes restored from previous sessions.
       Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
     }
 
     if (!tabData.entries) {
       tabData.entries = [];
     }
     if (tabData.extData) {
-      tab.__SS_extdata = Cu.cloneInto(tabData.extData, {});
+      TAB_CUSTOM_VALUES.set(tab, Cu.cloneInto(tabData.extData, {}));
     } else {
-      delete tab.__SS_extdata;
+      TAB_CUSTOM_VALUES.delete(tab);
     }
 
     // Tab is now open.
     delete tabData.closedAt;
 
     // Ensure the index is in bounds.
     let activeIndex = (tabData.index || tabData.entries.length) - 1;
     activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
@@ -3814,17 +3884,17 @@ var SessionStoreInternal = {
       // Start a new epoch to discard all frame script messages relating to a
       // previous epoch. All async messages that are still on their way to chrome
       // will be ignored and don't override any tab data set when restoring.
       let epoch = this.startNextEpoch(browser);
 
       // Ensure that the tab will get properly restored in the event the tab
       // crashes while restoring.  But don't set this on lazy browsers as
       // restoreTab will get called again when the browser is instantiated.
-      browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
+      TAB_STATE_FOR_BROWSER.set(browser, TAB_STATE_NEEDS_RESTORE);
 
       this._sendRestoreHistory(browser, {tabData, epoch, loadArguments});
 
       // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
       // it ensures each window will have its selected tab loaded.
       if (willRestoreImmediately) {
         this.restoreTabContent(tab, options);
       } else if (!forceOnDemand) {
@@ -3840,33 +3910,33 @@ var SessionStoreInternal = {
               tab.__test_connection_prepared = prepared;
               tab.__test_connection_url = url;
             }
           }
         }
         this.restoreNextTab();
       }
     } else {
-      // __SS_lazyData holds data for lazy-browser tabs to proxy for
+      // TAB_LAZY_STATES holds data for lazy-browser tabs to proxy for
       // data unobtainable from the unbound browser.  This only applies to lazy
       // browsers and will be removed once the browser is inserted in the document.
       // This must preceed `updateTabLabelAndIcon` call for required data to be present.
       let url = "about:blank";
       let title = "";
 
       if (activeIndex in tabData.entries) {
         url = tabData.entries[activeIndex].url;
         title = tabData.entries[activeIndex].title || url;
       }
-      tab.__SS_lazyData = {
+      TAB_LAZY_STATES.set(tab, {
         url,
         title,
         userTypedValue: tabData.userTypedValue || "",
         userTypedClear: tabData.userTypedClear || 0
-      };
+      });
     }
 
     if (tab.hasAttribute("customizemode")) {
       window.gCustomizeMode.setTab(tab);
     }
 
     // Update tab label and icon to show something
     // while we wait for the messages to be processed.
@@ -3888,17 +3958,17 @@ var SessionStoreInternal = {
     let loadArguments = aOptions.loadArguments;
     if (aTab.hasAttribute("customizemode") && !loadArguments) {
       return;
     }
 
     let browser = aTab.linkedBrowser;
     let window = aTab.ownerGlobal;
     let tabbrowser = window.gBrowser;
-    let tabData = TabState.clone(aTab);
+    let tabData = TabState.clone(aTab, TAB_CUSTOM_VALUES.get(aTab));
     let activeIndex = tabData.index - 1;
     let activePageData = tabData.entries[activeIndex] || null;
     let uri = activePageData ? activePageData.url || null : null;
     if (loadArguments) {
       uri = loadArguments.uri;
       if (loadArguments.userContextId) {
         browser.setAttribute("usercontextid", loadArguments.userContextId);
       }
@@ -3954,28 +4024,28 @@ var SessionStoreInternal = {
   /**
    * Marks a given pending tab as restoring.
    *
    * @param aTab
    *        the pending tab to mark as restoring
    */
   markTabAsRestoring(aTab) {
     let browser = aTab.linkedBrowser;
-    if (browser.__SS_restoreState != TAB_STATE_NEEDS_RESTORE) {
+    if (TAB_STATE_FOR_BROWSER.get(browser) != TAB_STATE_NEEDS_RESTORE) {
       throw new Error("Given tab is not pending.");
     }
 
     // Make sure that this tab is removed from the priority queue.
     TabRestoreQueue.remove(aTab);
 
     // Increase our internal count.
     this._tabsRestoringCount++;
 
     // Set this tab's state to restoring
-    browser.__SS_restoreState = TAB_STATE_RESTORING;
+    TAB_STATE_FOR_BROWSER.set(browser, TAB_STATE_RESTORING);
     aTab.removeAttribute("pending");
   },
 
   /**
    * This _attempts_ to restore the next available tab. If the restore fails,
    * then we will attempt the next one.
    * There are conditions where this won't do anything:
    *   if we're in the process of quitting
@@ -4204,38 +4274,34 @@ var SessionStoreInternal = {
   _updateSessionStartTime: function ssi_updateSessionStartTime(state) {
     // Attempt to load the session start time from the session state
     if (state.session && state.session.startTime) {
       this._sessionStartTime = state.session.startTime;
     }
   },
 
   /**
-   * call a callback for all currently opened browser windows
-   * (might miss the most recent one)
-   * @param aFunc
-   *        Callback each window is passed to
+   * Iterator that yields all currently opened browser windows, in order.
+   * (Might miss the most recent one.)
    */
-  _forEachBrowserWindow: function ssi_forEachBrowserWindow(aFunc) {
-    var windowsEnum = Services.wm.getEnumerator("navigator:browser");
-
-    while (windowsEnum.hasMoreElements()) {
-      var window = windowsEnum.getNext();
-      if (window.__SSi && !window.closed) {
-        aFunc.call(this, window);
+  _browserWindows: {
+    * [Symbol.iterator]() {
+      for (let window of BrowserWindowTracker.orderedWindows) {
+        if (window.__SSi && !window.closed)
+          yield window;
       }
     }
   },
 
   /**
    * Returns most recent window
    * @returns Window reference
    */
-  _getMostRecentBrowserWindow: function ssi_getMostRecentBrowserWindow() {
-    return RecentWindow.getMostRecentBrowserWindow({ allowPopups: true });
+  _getTopWindow: function ssi_getTopWindow() {
+    return BrowserWindowTracker.getTopWindow({ allowPopups: true });
   },
 
   /**
    * Calls onClose for windows that are determined to be closed but aren't
    * destroyed yet, which would otherwise cause getBrowserState and
    * setBrowserState to treat them as open windows.
    */
   _handleClosedWindows: function ssi_handleClosedWindows() {
@@ -4247,16 +4313,38 @@ var SessionStoreInternal = {
       if (window.closed) {
         promises.push(this.onClose(window));
       }
     }
     return Promise.all(promises);
   },
 
   /**
+   * Store a restore state of a window to this._statesToRestore. The window
+   * will be given an id that can be used to get the restore state from
+   * this._statesToRestore.
+   *
+   * @param window
+   *        a reference to a window that has a state to restore
+   * @param state
+   *        an object containing session data
+   */
+  _updateWindowRestoreState(window, state) {
+    // Store z-index, so that windows can be restored in reversed z-order.
+    if ("zIndex" in state.windows[0]) {
+      WINDOW_RESTORE_ZINDICES.set(window, state.windows[0].zIndex);
+    }
+    do {
+      var ID = "window" + Math.random();
+    } while (ID in this._statesToRestore);
+    WINDOW_RESTORE_IDS.set(window, ID);
+    this._statesToRestore[ID] = state;
+  },
+
+  /**
    * open a new browser window for a given session state
    * called when restoring a multi-window session
    * @param aState
    *        Object containing session data
    */
   _openWindowWithState: function ssi_openWindowWithState(aState) {
     var argString = Cc["@mozilla.org/supports-string;1"].
                     createInstance(Ci.nsISupportsString);
@@ -4274,20 +4362,18 @@ var SessionStoreInternal = {
     if (winState.isPrivate) {
       features += ",private";
     }
 
     var window =
       Services.ww.openWindow(null, this._prefBranch.getCharPref("chromeURL"),
                              "_blank", features, argString);
 
-    do {
-      var ID = "window" + Math.random();
-    } while (ID in this._statesToRestore);
-    this._statesToRestore[(window.__SS_restoreID = ID)] = aState;
+    this._updateWindowRestoreState(window, aState);
+    WINDOW_SHOWING_PROMISES.set(window, PromiseUtils.defer());
 
     return window;
   },
 
   /**
    * Whether or not to resume session, if not recovering from a crash.
    * @returns bool
    */
@@ -4602,17 +4688,17 @@ var SessionStoreInternal = {
   _setWindowStateBusyValue:
     function ssi_changeWindowStateBusyValue(aWindow, aValue) {
 
     this._windows[aWindow.__SSi].busy = aValue;
 
     // Keep the to-be-restored state in sync because that is returned by
     // getWindowState() as long as the window isn't loaded, yet.
     if (!this._isWindowLoaded(aWindow)) {
-      let stateToRestore = this._statesToRestore[aWindow.__SS_restoreID].windows[0];
+      let stateToRestore = this._statesToRestore[WINDOW_RESTORE_IDS.get(aWindow)].windows[0];
       stateToRestore.busy = aValue;
     }
   },
 
   /**
    * Set the given window's state to 'not busy'.
    * @param aWindow the window
    */
@@ -4683,17 +4769,17 @@ var SessionStoreInternal = {
 
   /**
    * @param aWindow
    *        Window reference
    * @returns whether this window's data is still cached in _statesToRestore
    *          because it's not fully loaded yet
    */
   _isWindowLoaded: function ssi_isWindowLoaded(aWindow) {
-    return !aWindow.__SS_restoreID;
+    return !WINDOW_RESTORE_IDS.has(aWindow);
   },
 
   /**
    * Resize this._closedWindows to the value of the pref, except in the case
    * where we don't have any non-popup windows on Windows and Linux. Then we must
    * resize such that we have at least one non-popup window.
    */
   _capClosedWindows: function ssi_capClosedWindows() {
@@ -4744,25 +4830,25 @@ var SessionStoreInternal = {
    *
    * @param aTab
    *        The tab that will be "reset"
    */
   _resetLocalTabRestoringState(aTab) {
     let browser = aTab.linkedBrowser;
 
     // Keep the tab's previous state for later in this method
-    let previousState = browser.__SS_restoreState;
+    let previousState = TAB_STATE_FOR_BROWSER.get(browser);
 
     if (!previousState) {
       Cu.reportError("Given tab is not restoring.");
       return;
     }
 
     // The browser is no longer in any sort of restoring state.
-    delete browser.__SS_restoreState;
+    TAB_STATE_FOR_BROWSER.delete(browser);
 
     aTab.removeAttribute("pending");
 
     if (previousState == TAB_STATE_RESTORING) {
       if (this._tabsRestoringCount)
         this._tabsRestoringCount--;
     } else if (previousState == TAB_STATE_NEEDS_RESTORE) {
       // Make sure that the tab is removed from the list of tabs to restore.
@@ -4770,17 +4856,17 @@ var SessionStoreInternal = {
       // for this tab.
       TabRestoreQueue.remove(aTab);
     }
   },
 
   _resetTabRestoringState(tab) {
     let browser = tab.linkedBrowser;
 
-    if (!browser.__SS_restoreState) {
+    if (!TAB_STATE_FOR_BROWSER.has(browser)) {
       Cu.reportError("Given tab is not restoring.");
       return;
     }
 
     browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {});
     this._resetLocalTabRestoringState(tab);
   },
 
--- a/browser/components/sessionstore/TabState.jsm
+++ b/browser/components/sessionstore/TabState.jsm
@@ -20,22 +20,22 @@ ChromeUtils.defineModuleGetter(this, "Ut
 /**
  * Module that contains tab state collection methods.
  */
 var TabState = Object.freeze({
   update(browser, data) {
     TabStateInternal.update(browser, data);
   },
 
-  collect(tab) {
-    return TabStateInternal.collect(tab);
+  collect(tab, extData) {
+    return TabStateInternal.collect(tab, extData);
   },
 
-  clone(tab) {
-    return TabStateInternal.clone(tab);
+  clone(tab, extData) {
+    return TabStateInternal.clone(tab, extData);
   },
 
   copyFromCache(browser, tabData, options) {
     TabStateInternal.copyFromCache(browser, tabData, options);
   },
 });
 
 var TabStateInternal = {
@@ -46,46 +46,51 @@ var TabStateInternal = {
     TabStateCache.update(browser, data);
   },
 
   /**
    * Collect data related to a single tab, synchronously.
    *
    * @param tab
    *        tabbrowser tab
+   * @param [extData]
+   *        optional dictionary object, containing custom tab values.
    *
    * @returns {TabData} An object with the data for this tab.  If the
    * tab has not been invalidated since the last call to
    * collect(aTab), the same object is returned.
    */
-  collect(tab) {
-    return this._collectBaseTabData(tab);
+  collect(tab, extData) {
+    return this._collectBaseTabData(tab, {extData});
   },
 
   /**
    * Collect data related to a single tab, including private data.
    * Use with caution.
    *
    * @param tab
    *        tabbrowser tab
+   * @param [extData]
+   *        optional dictionary object, containing custom tab values.
    *
    * @returns {object} An object with the data for this tab. This data is never
    *                   cached, it will always be read from the tab and thus be
    *                   up-to-date.
    */
-  clone(tab) {
-    return this._collectBaseTabData(tab, {includePrivateData: true});
+  clone(tab, extData) {
+    return this._collectBaseTabData(tab, {extData, includePrivateData: true});
   },
 
   /**
    * Collects basic tab data for a given tab.
    *
    * @param tab
    *        tabbrowser tab
    * @param options (object)
+   *        {extData: object} optional dictionary object, containing custom tab values
    *        {includePrivateData: true} to always include private data
    *
    * @returns {object} An object with the basic data for this tab.
    */
   _collectBaseTabData(tab, options) {
     let tabData = { entries: [], lastAccessed: tab.lastAccessed };
     let browser = tab.linkedBrowser;
 
@@ -100,18 +105,18 @@ var TabStateInternal = {
       tabData.muteReason = tab.muteReason;
     }
 
     tabData.mediaBlocked = browser.mediaBlocked;
 
     // Save tab attributes.
     tabData.attributes = TabAttributes.get(tab);
 
-    if (tab.__SS_extdata) {
-      tabData.extData = tab.__SS_extdata;
+    if (options.extData) {
+      tabData.extData = options.extData;
     }
 
     // Copy data from the tab state cache only if the tab has fully finished
     // restoring. We don't want to overwrite data contained in __SS_data.
     this.copyFromCache(browser, tabData, options);
 
     // After copyFromCache() was called we check for properties that are kept
     // in the cache only while the tab is pending or restoring. Once that
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -266,8 +266,9 @@ skip-if = debug
 [browser_tabicon_after_bg_tab_crash.js]
 skip-if = !crashreporter || !e10s # Tabs can't crash without e10s
 
 [browser_cookies.js]
 [browser_cookies_legacy.js]
 [browser_cookies_privacy.js]
 [browser_speculative_connect.js]
 [browser_1446343-windowsize.js]
+[browser_restore_reversed_z_order.js]
--- a/browser/components/sessionstore/test/browser_354894_perwindowpb.js
+++ b/browser/components/sessionstore/test/browser_354894_perwindowpb.js
@@ -19,24 +19,24 @@
  * not enabled on that platform (platform shim; the application is kept running
  * although there are no windows left)
  * @note There is a difference when closing a browser window with
  * BrowserTryToCloseWindow() as opposed to close(). The former will make
  * nsSessionStore restore a window next time it gets a chance and will post
  * notifications. The latter won't.
  */
 
-// The rejection "RecentWindow.getMostRecentBrowserWindow(...) is null" is left
+// The rejection "BrowserWindowTracker.getTopWindow(...) is null" is left
 // unhandled in some cases. This bug should be fixed, but for the moment this
 // file is whitelisted.
 //
 // NOTE: Whitelisting a class of rejections should be limited. Normally you
 //       should use "expectUncaughtRejection" to flag individual failures.
 ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", this);
-PromiseTestUtils.whitelistRejectionsGlobally(/getMostRecentBrowserWindow/);
+PromiseTestUtils.whitelistRejectionsGlobally(/getTopWindow/);
 
 // Some urls that might be opened in tabs and/or popups
 // Do not use about:blank:
 // That one is reserved for special purposes in the tests
 const TEST_URLS = ["about:mozilla", "about:buildconfig"];
 
 // Number of -request notifications to except
 // remember to adjust when adding new tests
@@ -434,18 +434,16 @@ add_task(async function test_open_close_
     await BrowserTestUtils.waitForEvent(newWin, "load");
 
     // Make sure we wait until this window is restored.
     await BrowserTestUtils.waitForEvent(newWin.gBrowser.tabContainer,
                                         "SSTabRestored");
 
     newWin2 = await promiseNewWindowLoaded();
 
-    is(newWin2.gBrowser.browsers.length, 1,
-       "Did not restore, as undoCloseWindow() was last called");
     is(TEST_URLS.indexOf(newWin2.gBrowser.browsers[0].currentURI.spec), -1,
        "Did not restore, as undoCloseWindow() was last called (2)");
 
     counts = getBrowserWindowsCount();
     is(counts.open, 2, "Got right number of open windows");
     is(counts.winstates, 3, "Got right number of window states");
 
     await BrowserTestUtils.closeWindow(newWin);
--- a/browser/components/sessionstore/test/browser_423132.js
+++ b/browser/components/sessionstore/test/browser_423132.js
@@ -31,17 +31,17 @@ add_task(async function() {
     i++;
   }
   Assert.equal(i, 1, "expected one cookie");
 
   // remove the cookie
   Services.cookies.removeAll();
 
   // restore the window state
-  ss.setBrowserState(state);
+  await setBrowserState(state);
 
   // at this point, the cookie should be restored...
   enumerator = Services.cookies.enumerator;
   let cookie2;
   while (enumerator.hasMoreElements()) {
     cookie2 = enumerator.getNext().QueryInterface(Ci.nsICookie);
     if (cookie.name == cookie2.name)
       break;
--- a/browser/components/sessionstore/test/browser_461634.js
+++ b/browser/components/sessionstore/test/browser_461634.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/. */
 
 ChromeUtils.import("resource:///modules/sessionstore/SessionStore.jsm");
 
-function test() {
+add_task(async function testClosedTabData() {
   /** Test for Bug 461634 **/
 
-  waitForExplicitFinish();
-
   const REMEMBER = Date.now(), FORGET = Math.random();
   let test_state = { windows: [{ "tabs": [{ "entries": [] }], _closedTabs: [
     { state: { entries: [{ url: "http://www.example.net/" }] }, title: FORGET },
     { state: { entries: [{ url: "http://www.example.net/" }] }, title: REMEMBER },
     { state: { entries: [{ url: "http://www.example.net/" }] }, title: FORGET },
     { state: { entries: [{ url: "http://www.example.net/" }] }, title: REMEMBER },
   ] }] };
   let remember_count = 2;
@@ -28,57 +26,57 @@ function test() {
       return false;
     } catch (ex) {
       return ex.name == "NS_ERROR_ILLEGAL_VALUE";
     }
   }
 
   // Open a window and add the above closed tab list.
   let newWin = openDialog(location, "", "chrome,all,dialog=no");
-  promiseWindowLoaded(newWin).then(() => {
-    Services.prefs.setIntPref("browser.sessionstore.max_tabs_undo",
-                              test_state.windows[0]._closedTabs.length);
-    ss.setWindowState(newWin, JSON.stringify(test_state), true);
+  await promiseWindowLoaded(newWin);
 
-    let closedTabs = SessionStore.getClosedTabData(newWin, false);
+  Services.prefs.setIntPref("browser.sessionstore.max_tabs_undo",
+                            test_state.windows[0]._closedTabs.length);
+  await setWindowState(newWin, test_state);
+
+  let closedTabs = SessionStore.getClosedTabData(newWin, false);
 
-    // Verify that non JSON serialized data is the same as JSON serialized data.
-    is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
-       "Non-serialized data is the same as serialized data");
+  // Verify that non JSON serialized data is the same as JSON serialized data.
+  is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
+     "Non-serialized data is the same as serialized data");
 
-    is(closedTabs.length, test_state.windows[0]._closedTabs.length,
-       "Closed tab list has the expected length");
-    is(countByTitle(closedTabs, FORGET),
-       test_state.windows[0]._closedTabs.length - remember_count,
-       "The correct amout of tabs are to be forgotten");
-    is(countByTitle(closedTabs, REMEMBER), remember_count,
-       "Everything is set up");
+  is(closedTabs.length, test_state.windows[0]._closedTabs.length,
+     "Closed tab list has the expected length");
+  is(countByTitle(closedTabs, FORGET),
+     test_state.windows[0]._closedTabs.length - remember_count,
+     "The correct amout of tabs are to be forgotten");
+  is(countByTitle(closedTabs, REMEMBER), remember_count,
+     "Everything is set up");
 
-    // All of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE.
-    ok(testForError(() => ss.forgetClosedTab({}, 0)),
-       "Invalid window for forgetClosedTab throws");
-    ok(testForError(() => ss.forgetClosedTab(newWin, -1)),
-       "Invalid tab for forgetClosedTab throws");
-    ok(testForError(() => ss.forgetClosedTab(newWin, test_state.windows[0]._closedTabs.length + 1)),
-       "Invalid tab for forgetClosedTab throws");
+  // All of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE.
+  ok(testForError(() => ss.forgetClosedTab({}, 0)),
+     "Invalid window for forgetClosedTab throws");
+  ok(testForError(() => ss.forgetClosedTab(newWin, -1)),
+     "Invalid tab for forgetClosedTab throws");
+  ok(testForError(() => ss.forgetClosedTab(newWin, test_state.windows[0]._closedTabs.length + 1)),
+     "Invalid tab for forgetClosedTab throws");
 
-    // Remove third tab, then first tab.
-    ss.forgetClosedTab(newWin, 2);
-    ss.forgetClosedTab(newWin, null);
+  // Remove third tab, then first tab.
+  ss.forgetClosedTab(newWin, 2);
+  ss.forgetClosedTab(newWin, null);
 
-    closedTabs = SessionStore.getClosedTabData(newWin, false);
+  closedTabs = SessionStore.getClosedTabData(newWin, false);
 
-    // Verify that non JSON serialized data is the same as JSON serialized data.
-    is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
-       "Non-serialized data is the same as serialized data");
+  // Verify that non JSON serialized data is the same as JSON serialized data.
+  is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
+     "Non-serialized data is the same as serialized data");
 
-    is(closedTabs.length, remember_count,
-       "The correct amout of tabs was removed");
-    is(countByTitle(closedTabs, FORGET), 0,
-       "All tabs specifically forgotten were indeed removed");
-    is(countByTitle(closedTabs, REMEMBER), remember_count,
-       "... and tabs not specifically forgetten weren't");
+  is(closedTabs.length, remember_count,
+     "The correct amout of tabs was removed");
+  is(countByTitle(closedTabs, FORGET), 0,
+     "All tabs specifically forgotten were indeed removed");
+  is(countByTitle(closedTabs, REMEMBER), remember_count,
+     "... and tabs not specifically forgetten weren't");
 
-    // Clean up.
-    Services.prefs.clearUserPref("browser.sessionstore.max_tabs_undo");
-    BrowserTestUtils.closeWindow(newWin).then(finish);
-  });
-}
+  // Clean up.
+  Services.prefs.clearUserPref("browser.sessionstore.max_tabs_undo");
+  await BrowserTestUtils.closeWindow(newWin);
+});
--- a/browser/components/sessionstore/test/browser_464199.js
+++ b/browser/components/sessionstore/test/browser_464199.js
@@ -52,16 +52,17 @@ add_task(async function() {
   }
 
   // open a window and add the above closed tab list
   let newWin = openDialog(location, "", "chrome,all,dialog=no");
   await promiseWindowLoaded(newWin);
   Services.prefs.setIntPref("browser.sessionstore.max_tabs_undo",
                             test_state.windows[0]._closedTabs.length);
   ss.setWindowState(newWin, JSON.stringify(test_state), true);
+  await promiseWindowRestored(newWin);
 
   let closedTabs = JSON.parse(ss.getClosedTabData(newWin));
   is(closedTabs.length, test_state.windows[0]._closedTabs.length,
      "Closed tab list has the expected length");
   is(countByTitle(closedTabs, FORGET),
      test_state.windows[0]._closedTabs.length - remember_count,
      "The correct amout of tabs are to be forgotten");
   is(countByTitle(closedTabs, REMEMBER), remember_count,
--- a/browser/components/sessionstore/test/browser_465223.js
+++ b/browser/components/sessionstore/test/browser_465223.js
@@ -1,45 +1,41 @@
 /* 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/. */
 
-function test() {
+add_task(async function test_clearWindowValues() {
   /** Test for Bug 465223 **/
 
-  // test setup
-  waitForExplicitFinish();
-
   let uniqueKey1 = "bug 465223.1";
   let uniqueKey2 = "bug 465223.2";
   let uniqueValue1 = "unik" + Date.now();
   let uniqueValue2 = "pi != " + Math.random();
 
   // open a window and set a value on it
   let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
-  promiseWindowLoaded(newWin).then(() => {
-    ss.setWindowValue(newWin, uniqueKey1, uniqueValue1);
+  await promiseWindowLoaded(newWin);
+  ss.setWindowValue(newWin, uniqueKey1, uniqueValue1);
 
-    let newState = { windows: [{ tabs: [{ entries: [] }], extData: {} }] };
-    newState.windows[0].extData[uniqueKey2] = uniqueValue2;
-    ss.setWindowState(newWin, JSON.stringify(newState), false);
+  let newState = { windows: [{ tabs: [{ entries: [] }], extData: {} }] };
+  newState.windows[0].extData[uniqueKey2] = uniqueValue2;
+  await setWindowState(newWin, newState);
 
-    is(newWin.gBrowser.tabs.length, 2,
-       "original tab wasn't overwritten");
-    is(ss.getWindowValue(newWin, uniqueKey1), uniqueValue1,
-       "window value wasn't overwritten when the tabs weren't");
-    is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue2,
-       "new window value was correctly added");
+  is(newWin.gBrowser.tabs.length, 2,
+    "original tab wasn't overwritten");
+  is(ss.getWindowValue(newWin, uniqueKey1), uniqueValue1,
+    "window value wasn't overwritten when the tabs weren't");
+  is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue2,
+    "new window value was correctly added");
 
-    newState.windows[0].extData[uniqueKey2] = uniqueValue1;
-    ss.setWindowState(newWin, JSON.stringify(newState), true);
+  newState.windows[0].extData[uniqueKey2] = uniqueValue1;
+  await setWindowState(newWin, newState, true);
 
-    is(newWin.gBrowser.tabs.length, 1,
-       "original tabs were overwritten");
-    is(ss.getWindowValue(newWin, uniqueKey1), "",
-       "window value was cleared");
-    is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue1,
-       "window value was correctly overwritten");
+  is(newWin.gBrowser.tabs.length, 1,
+    "original tabs were overwritten");
+  is(ss.getWindowValue(newWin, uniqueKey1), "",
+    "window value was cleared");
+  is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue1,
+    "window value was correctly overwritten");
 
-    // clean up
-    BrowserTestUtils.closeWindow(newWin).then(finish);
-  });
-}
+  // clean up
+  await BrowserTestUtils.closeWindow(newWin);
+});
--- a/browser/components/sessionstore/test/browser_477657.js
+++ b/browser/components/sessionstore/test/browser_477657.js
@@ -1,60 +1,57 @@
 /* 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/. */
 
-function test() {
+add_task(async function test_sizemodeDefaults() {
   /** Test for Bug 477657 **/
-  waitForExplicitFinish();
-
   let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
-  promiseWindowLoaded(newWin).then(() => {
-    let newState = { windows: [{
-      tabs: [{ entries: [] }],
-      _closedTabs: [{
-        state: { entries: [{ url: "about:" }]},
-        title: "About:"
-      }],
-      sizemode: "maximized"
-    }] };
+  await promiseWindowLoaded(newWin);
+  let newState = { windows: [{
+    tabs: [{ entries: [] }],
+    _closedTabs: [{
+      state: { entries: [{ url: "about:" }]},
+      title: "About:"
+    }],
+    sizemode: "maximized"
+  }] };
 
-    let uniqueKey = "bug 477657";
-    let uniqueValue = "unik" + Date.now();
+  let uniqueKey = "bug 477657";
+  let uniqueValue = "unik" + Date.now();
+
+  ss.setWindowValue(newWin, uniqueKey, uniqueValue);
+  is(ss.getWindowValue(newWin, uniqueKey), uniqueValue,
+     "window value was set before the window was overwritten");
 
-    ss.setWindowValue(newWin, uniqueKey, uniqueValue);
-    is(ss.getWindowValue(newWin, uniqueKey), uniqueValue,
-       "window value was set before the window was overwritten");
-    ss.setWindowState(newWin, JSON.stringify(newState), true);
+  await setWindowState(newWin, newState, true);
+  // use newWin.setTimeout(..., 0) to mirror sss_restoreWindowFeatures
+  await new Promise(resolve => newWin.setTimeout(resolve, 0));
 
-    // use newWin.setTimeout(..., 0) to mirror sss_restoreWindowFeatures
-    newWin.setTimeout(function() {
-      is(ss.getWindowValue(newWin, uniqueKey), "",
-         "window value was implicitly cleared");
+  is(ss.getWindowValue(newWin, uniqueKey), "",
+    "window value was implicitly cleared");
 
-      is(newWin.windowState, newWin.STATE_MAXIMIZED,
-         "the window was maximized");
+  is(newWin.windowState, newWin.STATE_MAXIMIZED,
+    "the window was maximized");
 
-      is(JSON.parse(ss.getClosedTabData(newWin)).length, 1,
-         "the closed tab was added before the window was overwritten");
-      delete newState.windows[0]._closedTabs;
-      delete newState.windows[0].sizemode;
-      ss.setWindowState(newWin, JSON.stringify(newState), true);
+  is(JSON.parse(ss.getClosedTabData(newWin)).length, 1,
+    "the closed tab was added before the window was overwritten");
+  delete newState.windows[0]._closedTabs;
+  delete newState.windows[0].sizemode;
 
-      newWin.setTimeout(function() {
-        is(JSON.parse(ss.getClosedTabData(newWin)).length, 0,
-           "closed tabs were implicitly cleared");
+  await setWindowState(newWin, newState, true);
+  await new Promise(resolve => newWin.setTimeout(resolve, 0));
 
-        is(newWin.windowState, newWin.STATE_MAXIMIZED,
-           "the window remains maximized");
-        newState.windows[0].sizemode = "normal";
-        ss.setWindowState(newWin, JSON.stringify(newState), true);
+  is(JSON.parse(ss.getClosedTabData(newWin)).length, 0,
+    "closed tabs were implicitly cleared");
+
+  is(newWin.windowState, newWin.STATE_MAXIMIZED,
+    "the window remains maximized");
+  newState.windows[0].sizemode = "normal";
 
-        newWin.setTimeout(function() {
-          isnot(newWin.windowState, newWin.STATE_MAXIMIZED,
-                "the window was explicitly unmaximized");
+  await setWindowState(newWin, newState, true);
+  await new Promise(resolve => newWin.setTimeout(resolve, 0));
 
-          BrowserTestUtils.closeWindow(newWin).then(finish);
-        }, 0);
-      }, 0);
-    }, 0);
-  });
-}
+  isnot(newWin.windowState, newWin.STATE_MAXIMIZED,
+    "the window was explicitly unmaximized");
+
+  await BrowserTestUtils.closeWindow(newWin);
+});
--- a/browser/components/sessionstore/test/browser_490040.js
+++ b/browser/components/sessionstore/test/browser_490040.js
@@ -45,17 +45,17 @@ add_task(async function test_bug_490040(
     // Ensure we can store the window if needed.
     let startingClosedWindowCount = ss.getClosedWindowCount();
     await pushPrefs(["browser.sessionstore.max_windows_undo",
                      startingClosedWindowCount + 1]);
 
     let curClosedWindowCount = ss.getClosedWindowCount();
     let win = await BrowserTestUtils.openNewBrowserWindow();
 
-    ss.setWindowState(win, JSON.stringify(state.windowState), true);
+    await setWindowState(win, state.windowState, true);
     if (state.windowState.windows[0].tabs.length) {
       await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
     }
 
     await BrowserTestUtils.closeWindow(win);
 
     is(ss.getClosedWindowCount(),
        curClosedWindowCount + (state.shouldBeAdded ? 1 : 0),
--- a/browser/components/sessionstore/test/browser_491577.js
+++ b/browser/components/sessionstore/test/browser_491577.js
@@ -1,18 +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/. */
 
-function test() {
+add_task(async function test_deleteClosedWindow() {
   /** Test for Bug 491577 **/
 
-  // test setup
-  waitForExplicitFinish();
-
   const REMEMBER = Date.now(), FORGET = Math.random();
   let test_state = {
     windows: [ { tabs: [{ entries: [{ url: "http://example.com/", triggeringPrincipal_base64 }] }], selected: 1 } ],
     _closedWindows: [
       // _closedWindows[0]
       {
         tabs: [
           { entries: [{ url: "http://example.com/", triggeringPrincipal_base64, title: "title" }] },
@@ -75,45 +72,44 @@ function test() {
       return false;
     } catch (ex) {
       return ex.name == "NS_ERROR_ILLEGAL_VALUE";
     }
   }
 
   // open a window and add the above closed window list
   let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
-  promiseWindowLoaded(newWin).then(() => {
-    Services.prefs.setIntPref("browser.sessionstore.max_windows_undo",
-                              test_state._closedWindows.length);
-    ss.setWindowState(newWin, JSON.stringify(test_state), true);
+  await promiseWindowLoaded(newWin);
+  Services.prefs.setIntPref("browser.sessionstore.max_windows_undo",
+                            test_state._closedWindows.length);
+  await setWindowState(newWin, test_state, true);
 
-    let closedWindows = JSON.parse(ss.getClosedWindowData());
-    is(closedWindows.length, test_state._closedWindows.length,
-       "Closed window list has the expected length");
-    is(countByTitle(closedWindows, FORGET),
-       test_state._closedWindows.length - remember_count,
-       "The correct amount of windows are to be forgotten");
-    is(countByTitle(closedWindows, REMEMBER), remember_count,
-       "Everything is set up.");
+  let closedWindows = JSON.parse(ss.getClosedWindowData());
+  is(closedWindows.length, test_state._closedWindows.length,
+     "Closed window list has the expected length");
+  is(countByTitle(closedWindows, FORGET),
+     test_state._closedWindows.length - remember_count,
+     "The correct amount of windows are to be forgotten");
+  is(countByTitle(closedWindows, REMEMBER), remember_count,
+     "Everything is set up.");
 
-    // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
-    ok(testForError(() => ss.forgetClosedWindow(-1)),
-       "Invalid window for forgetClosedWindow throws");
-    ok(testForError(() => ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
-       "Invalid window for forgetClosedWindow throws");
+  // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
+  ok(testForError(() => ss.forgetClosedWindow(-1)),
+     "Invalid window for forgetClosedWindow throws");
+  ok(testForError(() => ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
+     "Invalid window for forgetClosedWindow throws");
 
-    // Remove third window, then first window
-    ss.forgetClosedWindow(2);
-    ss.forgetClosedWindow(null);
+  // Remove third window, then first window
+  ss.forgetClosedWindow(2);
+  ss.forgetClosedWindow(null);
 
-    closedWindows = JSON.parse(ss.getClosedWindowData());
-    is(closedWindows.length, remember_count,
-       "The correct amount of windows were removed");
-    is(countByTitle(closedWindows, FORGET), 0,
-       "All windows specifically forgotten were indeed removed");
-    is(countByTitle(closedWindows, REMEMBER), remember_count,
-       "... and windows not specifically forgetten weren't.");
+  closedWindows = JSON.parse(ss.getClosedWindowData());
+  is(closedWindows.length, remember_count,
+     "The correct amount of windows were removed");
+  is(countByTitle(closedWindows, FORGET), 0,
+     "All windows specifically forgotten were indeed removed");
+  is(countByTitle(closedWindows, REMEMBER), remember_count,
+     "... and windows not specifically forgetten weren't.");
 
-    // clean up
-    Services.prefs.clearUserPref("browser.sessionstore.max_windows_undo");
-    BrowserTestUtils.closeWindow(newWin).then(finish);
-  });
-}
+  // clean up
+  Services.prefs.clearUserPref("browser.sessionstore.max_windows_undo");
+  await BrowserTestUtils.closeWindow(newWin);
+});
--- a/browser/components/sessionstore/test/browser_495495.js
+++ b/browser/components/sessionstore/test/browser_495495.js
@@ -1,42 +1,34 @@
 /* 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/. */
 
-function test() {
+add_task(async function test_urlbarFocus() {
   /** Test for Bug 495495 **/
 
-  waitForExplicitFinish();
-
   let newWin = openDialog(location, "_blank", "chrome,all,dialog=no,toolbar=yes");
-  promiseWindowLoaded(newWin).then(() => {
-    let state1 = ss.getWindowState(newWin);
-    BrowserTestUtils.closeWindow(newWin).then(() => {
+  await promiseWindowLoaded(newWin);
+  let state1 = ss.getWindowState(newWin);
+  await BrowserTestUtils.closeWindow(newWin);
 
-      newWin = openDialog(location, "_blank",
-        "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar=no,location,personal,directories,dialog=no");
-      promiseWindowLoaded(newWin).then(() => {
-        let state2 = ss.getWindowState(newWin);
+  newWin = openDialog(location, "_blank",
+    "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar=no,location,personal,directories,dialog=no");
+  await promiseWindowLoaded(newWin);
+  let state2 = ss.getWindowState(newWin);
 
-        function testState(state, expected, callback) {
-          let win = openDialog(location, "_blank", "chrome,all,dialog=no");
-          promiseWindowLoaded(win).then(() => {
+  async function testState(state, expected) {
+    let win = openDialog(location, "_blank", "chrome,all,dialog=no");
+    await promiseWindowLoaded(win);
 
-            is(win.gURLBar.readOnly, false,
-               "URL bar should not be read-only before setting the state");
-            ss.setWindowState(win, state, true);
-            is(win.gURLBar.readOnly, expected.readOnly,
-               "URL bar read-only state should be restored correctly");
-
-            BrowserTestUtils.closeWindow(win).then(callback);
-          });
-        }
+    is(win.gURLBar.readOnly, false,
+       "URL bar should not be read-only before setting the state");
+    await setWindowState(win, state, true);
+    is(win.gURLBar.readOnly, expected.readOnly,
+       "URL bar read-only state should be restored correctly");
 
-        BrowserTestUtils.closeWindow(newWin).then(() => {
-          testState(state1, {readOnly: false}, function() {
-            testState(state2, {readOnly: true}, finish);
-          });
-        });
-      });
-    });
-  });
-}
+    await BrowserTestUtils.closeWindow(win);
+  }
+
+  await BrowserTestUtils.closeWindow(newWin);
+  await testState(state1, {readOnly: false});
+  await testState(state2, {readOnly: true});
+});
--- a/browser/components/sessionstore/test/browser_514751.js
+++ b/browser/components/sessionstore/test/browser_514751.js
@@ -1,36 +1,32 @@
 /* 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/. */
 
-function test() {
+add_task(async function test_malformedURI() {
   /** Test for Bug 514751 (Wallpaper) **/
 
-  waitForExplicitFinish();
-
   let state = {
     windows: [{
       tabs: [{
         entries: [
           { url: "about:mozilla", triggeringPrincipal_base64, title: "Mozilla" },
           {}
         ]
       }]
     }]
   };
 
   var theWin = openDialog(location, "", "chrome,all,dialog=no");
-  theWin.addEventListener("load", function() {
-    executeSoon(function() {
-      var gotError = false;
-      try {
-        ss.setWindowState(theWin, JSON.stringify(state), true);
-      } catch (e) {
-        if (/NS_ERROR_MALFORMED_URI/.test(e))
-          gotError = true;
-      }
-      ok(!gotError, "Didn't get a malformed URI error.");
-      BrowserTestUtils.closeWindow(theWin).then(finish);
-    });
-  }, {once: true});
-}
+  await promiseWindowLoaded(theWin);
 
+  var gotError = false;
+  try {
+    await setWindowState(theWin, state, true);
+  } catch (e) {
+    if (/NS_ERROR_MALFORMED_URI/.test(e))
+      gotError = true;
+  }
+
+  ok(!gotError, "Didn't get a malformed URI error.");
+  await BrowserTestUtils.closeWindow(theWin);
+});
--- a/browser/components/sessionstore/test/browser_595601-restore_hidden.js
+++ b/browser/components/sessionstore/test/browser_595601-restore_hidden.js
@@ -72,28 +72,28 @@ var TabsProgressListener = {
     this.callback = callback;
   },
 
   observe(browser) {
     TabsProgressListener.onRestored(browser);
   },
 
   onRestored(browser) {
-    if (this.callback && browser.__SS_restoreState == TAB_STATE_RESTORING)
+    if (this.callback && ss.getInternalObjectState(browser) == TAB_STATE_RESTORING)
       this.callback.apply(null, [this.window].concat(this.countTabs()));
   },
 
   countTabs() {
     let needsRestore = 0, isRestoring = 0;
 
     for (let i = 0; i < this.window.gBrowser.tabs.length; i++) {
-      let browser = this.window.gBrowser.tabs[i].linkedBrowser;
-      if (browser.__SS_restoreState == TAB_STATE_RESTORING)
+      let state = ss.getInternalObjectState(this.window.gBrowser.tabs[i].linkedBrowser);
+      if (state == TAB_STATE_RESTORING)
         isRestoring++;
-      else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
+      else if (state == TAB_STATE_NEEDS_RESTORE)
         needsRestore++;
     }
 
     return [needsRestore, isRestoring];
   }
 };
 
 // ----------
--- a/browser/components/sessionstore/test/browser_607016.js
+++ b/browser/components/sessionstore/test/browser_607016.js
@@ -77,17 +77,17 @@ add_task(async function() {
        "(creating) new data is stored in extData where there was none");
 
     while (gBrowser.tabs.length > 1) {
       BrowserTestUtils.removeTab(gBrowser.tabs[1]);
     }
   }
 
   // Set the test state.
-  ss.setBrowserState(JSON.stringify(state));
+  await setBrowserState(state);
 
   // Wait until the selected tab is restored and all others are pending.
   await Promise.all(Array.map(gBrowser.tabs, tab => {
     return (tab == gBrowser.selectedTab) ?
       promiseTabRestored(tab) : promiseTabRestoring(tab);
   }));
 
   // Kick off the actual tests.
--- a/browser/components/sessionstore/test/browser_636279.js
+++ b/browser/components/sessionstore/test/browser_636279.js
@@ -53,20 +53,20 @@ function countTabs() {
   let windowsEnum = Services.wm.getEnumerator("navigator:browser");
 
   while (windowsEnum.hasMoreElements()) {
     let window = windowsEnum.getNext();
     if (window.closed)
       continue;
 
     for (let i = 0; i < window.gBrowser.tabs.length; i++) {
-      let browser = window.gBrowser.tabs[i].linkedBrowser;
-      if (browser.__SS_restoreState == TAB_STATE_RESTORING)
+      let browserState = ss.getInternalObjectState(window.gBrowser.tabs[i].linkedBrowser);
+      if (browserState == TAB_STATE_RESTORING)
         isRestoring++;
-      else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
+      else if (browserState == TAB_STATE_NEEDS_RESTORE)
         needsRestore++;
     }
   }
 
   return [needsRestore, isRestoring];
 }
 
 var TabsProgressListener = {
@@ -87,13 +87,13 @@ var TabsProgressListener = {
     delete this.callback;
   },
 
   observe(browser, topic, data) {
     TabsProgressListener.onRestored(browser);
   },
 
   onRestored(browser) {
-    if (this.callback && browser.__SS_restoreState == TAB_STATE_RESTORING) {
+    if (this.callback && ss.getInternalObjectState(browser) == TAB_STATE_RESTORING) {
       this.callback.apply(null, countTabs());
     }
   }
 };
--- a/browser/components/sessionstore/test/browser_637020.js
+++ b/browser/components/sessionstore/test/browser_637020.js
@@ -37,16 +37,17 @@ add_task(async function test() {
     }, "domwindowopened");
   });
 
   // Set the new browser state that will
   // restore a window with two slowly loading tabs.
   let backupState = SessionStore.getBrowserState();
   SessionStore.setBrowserState(JSON.stringify(TEST_STATE));
   let win = await promiseWindow;
+  await promiseWindowRestored(win);
 
   // The window has now been opened. Check the state that is returned,
   // this should come from the cache while the window isn't restored, yet.
   info("the window has been opened");
   checkWindows();
 
   // The history has now been restored and the tabs are loading. The data must
   // now come from the window, if it's correctly been marked as dirty before.
--- a/browser/components/sessionstore/test/browser_687710.js
+++ b/browser/components/sessionstore/test/browser_687710.js
@@ -32,17 +32,17 @@ var state = {windows: [{tabs: [{entries:
         docIdentifier: 1,
         url: "http://example.com",
         triggeringPrincipal_base64,
       }
     ]
   }
 ]}]}]};
 
-function test() {
+add_task(async function test() {
   registerCleanupFunction(function() {
     ss.setBrowserState(stateBackup);
   });
 
   /* This test fails by hanging. */
-  ss.setBrowserState(JSON.stringify(state));
+  await setBrowserState(state);
   ok(true, "Didn't hang!");
-}
+});
--- a/browser/components/sessionstore/test/browser_694378.js
+++ b/browser/components/sessionstore/test/browser_694378.js
@@ -1,32 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test Summary:
 // 1.  call ss.setWindowState with a broken state
 // 1a. ensure that it doesn't throw.
 
-function test() {
-  waitForExplicitFinish();
-
+add_task(async function test_brokenWindowState() {
   let brokenState = {
     windows: [
       { tabs: [{ entries: [{ url: "about:mozilla", triggeringPrincipal_base64 }] }] }
     ],
     selectedWindow: 2
   };
-  let brokenStateString = JSON.stringify(brokenState);
 
   let gotError = false;
   try {
-    ss.setWindowState(window, brokenStateString, true);
+    await setWindowState(window, brokenState, true);
   } catch (ex) {
     gotError = true;
     info(ex);
   }
 
   ok(!gotError, "ss.setWindowState did not throw an error");
 
   // Make sure that we reset the state. Use a full state just in case things get crazy.
   let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }]}]};
-  waitForBrowserState(blankState, finish);
-}
+  await promiseBrowserState(blankState);
+});
--- a/browser/components/sessionstore/test/browser_739805.js
+++ b/browser/components/sessionstore/test/browser_739805.js
@@ -18,17 +18,17 @@ function test() {
 
   let tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
   let browser = tab.linkedBrowser;
 
   promiseBrowserLoaded(browser).then(() => {
     isnot(gBrowser.selectedTab, tab, "newly created tab is not selected");
 
     ss.setTabState(tab, JSON.stringify(tabState));
-    is(browser.__SS_restoreState, TAB_STATE_NEEDS_RESTORE, "tab needs restoring");
+    is(ss.getInternalObjectState(browser), TAB_STATE_NEEDS_RESTORE, "tab needs restoring");
 
     let {formdata} = JSON.parse(ss.getTabState(tab));
     is(formdata && formdata.id.foo, "bar", "tab state's formdata is valid");
 
     promiseTabRestored(tab).then(() => {
       ContentTask.spawn(browser, null, function() {
         let input = content.document.getElementById("foo");
         is(input.value, "bar", "formdata has been restored correctly");
--- a/browser/components/sessionstore/test/browser_frame_history.js
+++ b/browser/components/sessionstore/test/browser_frame_history.js
@@ -96,24 +96,24 @@ add_task(async function() {
        "frame " + i + " has the right url");
   }
   gBrowser.removeTab(newTab);
 });
 
 // Now, test that we don't record history if the iframe is added dynamically
 add_task(async function() {
   // Start with an empty history
-    let blankState = JSON.stringify({
-      windows: [{
-        tabs: [{ entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }],
-        _closedTabs: []
-      }],
-      _closedWindows: []
-    });
-    ss.setBrowserState(blankState);
+  let blankState = JSON.stringify({
+    windows: [{
+      tabs: [{ entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }],
+      _closedTabs: []
+    }],
+    _closedWindows: []
+  });
+  await setBrowserState(blankState);
 
   let testURL = getRootDirectory(gTestPath) + "browser_frame_history_index_blank.html";
   let tab = BrowserTestUtils.addTab(gBrowser, testURL);
   gBrowser.selectedTab = tab;
   await waitForLoadsInBrowser(tab.linkedBrowser, 1);
 
   info("dynamic: Opening a page with an iframe containing three frames, 4 dynamic loads should take place");
   let doc = tab.linkedBrowser.contentDocument;
--- a/browser/components/sessionstore/test/browser_merge_closed_tabs.js
+++ b/browser/components/sessionstore/test/browser_merge_closed_tabs.js
@@ -31,22 +31,22 @@ add_task(async function() {
     }]
   };
 
   const maxTabsUndo = 4;
   Services.prefs.setIntPref("browser.sessionstore.max_tabs_undo", maxTabsUndo);
 
   // Open a new window and restore it to an initial state.
   let win = await promiseNewWindowLoaded({private: false});
-  SessionStore.setWindowState(win, JSON.stringify(initialState), true);
+  await setWindowState(win, initialState, true);
   is(SessionStore.getClosedTabCount(win), 2, "2 closed tabs after restoring initial state");
 
   // Restore the new state but do not overwrite existing tabs (this should
   // cause the closed tabs to be merged).
-  SessionStore.setWindowState(win, JSON.stringify(restoreState), false);
+  await setWindowState(win, restoreState);
 
   // Verify the windows closed tab data is correct.
   let iClosed = initialState.windows[0]._closedTabs;
   let rClosed = restoreState.windows[0]._closedTabs;
   let cData = JSON.parse(SessionStore.getClosedTabData(win));
 
   is(cData.length, Math.min(iClosed.length + rClosed.length, maxTabsUndo),
      "Number of closed tabs is correct");
--- a/browser/components/sessionstore/test/browser_pending_tabs.js
+++ b/browser/components/sessionstore/test/browser_pending_tabs.js
@@ -21,17 +21,17 @@ add_task(async function() {
   ss.setTabState(tab, JSON.stringify(TAB_STATE));
   ok(tab.hasAttribute("pending"), "tab is pending");
   await promise;
 
   // Flush to ensure the parent has all data.
   await TabStateFlusher.flush(browser);
 
   // Check that the shistory index is the one we restored.
-  let tabState = TabState.collect(tab);
+  let tabState = TabState.collect(tab, ss.getInternalObjectState(tab));
   is(tabState.index, TAB_STATE.index, "correct shistory index");
 
   // Check we don't collect userTypedValue when we shouldn't.
   ok(!tabState.userTypedValue, "tab didn't have a userTypedValue");
 
   // Cleanup.
   gBrowser.removeTab(tab);
 });
--- a/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js
+++ b/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js
@@ -129,17 +129,17 @@ async function runScenarios(scenarios) {
     if (tabbrowser.selectedTab != tabToSelect) {
       await BrowserTestUtils.switchTab(tabbrowser, tabToSelect);
     }
 
     // Okay, time to test!
     let state = prepareState(scenario.stateToRestore,
                              scenario.selectedTab);
 
-    SessionStore.setWindowState(win, state, true);
+    await setWindowState(win, state, true);
 
     for (let i = 0; i < scenario.expectedRemoteness.length; ++i) {
       let expectedRemoteness = scenario.expectedRemoteness[i];
       let tab = tabbrowser.tabs[i];
 
       Assert.equal(tab.linkedBrowser.isRemoteBrowser, expectedRemoteness,
                    "Should have gotten the expected remoteness " +
                    `for the tab at index ${i}`);
--- a/browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js
+++ b/browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js
@@ -129,17 +129,17 @@ add_task(async function run_test() {
 
   // Clear cookies.
   Services.cookies.removeAll();
 
   // Open a new window.
   let win = await promiseNewWindowLoaded();
 
   // Restore window with session cookies that have no originAttributes.
-  ss.setWindowState(win, SESSION_DATA, true);
+  await setWindowState(win, SESSION_DATA, true);
 
   let enumerator = Services.cookies.getCookiesFromHost(TEST_HOST, {});
   let cookie;
   let cookieCount = 0;
   while (enumerator.hasMoreElements()) {
     cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
     cookieCount++;
   }
@@ -149,17 +149,17 @@ add_task(async function run_test() {
   is(cookie.name, COOKIE.name, "cookie name successfully restored");
   is(cookie.value, COOKIE.value, "cookie value successfully restored");
   is(cookie.path, COOKIE.path, "cookie path successfully restored");
 
   // Clear cookies.
   Services.cookies.removeAll();
 
   // Restore window with session cookies that have originAttributes within.
-  ss.setWindowState(win, SESSION_DATA_OA, true);
+  await setWindowState(win, SESSION_DATA_OA, true);
 
   enumerator = Services.cookies.getCookiesFromHost(TEST_HOST, {});
   cookieCount = 0;
   while (enumerator.hasMoreElements()) {
     cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
     cookieCount++;
   }
 
--- a/browser/components/sessionstore/test/browser_restore_redirect.js
+++ b/browser/components/sessionstore/test/browser_restore_redirect.js
@@ -18,17 +18,17 @@ add_task(async function check_http_redir
 
   info("Restored tab");
 
   await TabStateFlusher.flush(browser);
   let data = TabState.collect(tab);
   is(data.entries.length, 1, "Should be one entry in session history");
   is(data.entries[0].url, TARGET, "Should be the right session history entry");
 
-  ok(!("__SS_data" in browser), "Temporary restore data should have been cleared");
+  ok(!ss.getInternalObjectState(browser), "Temporary restore data should have been cleared");
 
   // Cleanup.
   BrowserTestUtils.removeTab(tab);
 });
 
 /**
  * Ensure that a js redirect leaves a working tab.
  */
@@ -57,13 +57,13 @@ add_task(async function check_js_redirec
 
   await loadPromise;
 
   await TabStateFlusher.flush(browser);
   let data = TabState.collect(tab);
   is(data.entries.length, 1, "Should be one entry in session history");
   is(data.entries[0].url, TARGET, "Should be the right session history entry");
 
-  ok(!("__SS_data" in browser), "Temporary restore data should have been cleared");
+  ok(!ss.getInternalObjectState(browser), "Temporary restore data should have been cleared");
 
   // Cleanup.
   BrowserTestUtils.removeTab(tab);
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_restore_reversed_z_order.js
@@ -0,0 +1,77 @@
+"use strict";
+
+const PRIMARY_WINDOW = window;
+
+let gTestURLsMap = new Map([
+  ["about:about", null],
+  ["about:license", null],
+  ["about:robots", null],
+  ["about:mozilla", null]
+]);
+let gBrowserState;
+
+add_task(async function setup() {
+  let windows = [];
+  let count = 0;
+  for (let url of gTestURLsMap.keys()) {
+    let window = !count ? PRIMARY_WINDOW : await BrowserTestUtils.openNewBrowserWindow();
+    let browserLoaded = BrowserTestUtils.browserLoaded(window.gBrowser.selectedBrowser);
+    await BrowserTestUtils.loadURI(window.gBrowser.selectedBrowser, url);
+    await browserLoaded;
+    // Capture the title.
+    gTestURLsMap.set(url, window.gBrowser.selectedTab.label);
+    // Minimize the before-last window, to have a different window feature added
+    // to the test.
+    if (count == gTestURLsMap.size - 1) {
+      let activated = BrowserTestUtils.waitForEvent(windows[count - 1], "activate");
+      window.minimize();
+      await activated;
+    }
+    windows.push(window);
+    ++count;
+  }
+
+  // Wait until we get the lastest history from all windows.
+  await Promise.all(windows.map(window => TabStateFlusher.flushWindow(window)));
+
+  gBrowserState = ss.getBrowserState();
+
+  await promiseAllButPrimaryWindowClosed();
+});
+
+add_task(async function test_z_indices_are_saved_correctly() {
+  let state = JSON.parse(gBrowserState);
+  Assert.equal(state.windows.length, gTestURLsMap.size, "Correct number of windows saved");
+
+  // Check if we saved state in correct order of creation.
+  let idx = 0;
+  for (let url of gTestURLsMap.keys()) {
+    Assert.equal(state.windows[idx].tabs[0].entries[0].url, url,
+      `Window #${idx} is stored in correct creation order`);
+    ++idx;
+  }
+
+  // Check if we saved a valid zIndex (no null, no undefined or no 0).
+  for (let window of state.windows) {
+    Assert.ok(window.zIndex, "A valid zIndex is stored");
+  }
+
+  Assert.equal(state.windows[0].zIndex, 3, "Window #1 should have the correct z-index");
+  Assert.equal(state.windows[1].zIndex, 2, "Window #2 should have correct z-index");
+  Assert.equal(state.windows[2].zIndex, 1, "Window #3 should be the topmost window");
+  Assert.equal(state.windows[3].zIndex, 4, "Minimized window should be the last window to restore");
+});
+
+add_task(async function test_windows_are_restored_in_reversed_z_order() {
+  await promiseBrowserState(gBrowserState);
+
+  let indexedTabLabels = [...gTestURLsMap.values()];
+  let tabsRestoredLabels = [...BrowserWindowTracker.orderedWindows].map(window => window.gBrowser.selectedTab.label);
+
+  Assert.equal(tabsRestoredLabels[0], indexedTabLabels[2], "First restored tab should be last used tab");
+  Assert.equal(tabsRestoredLabels[1], indexedTabLabels[1], "Second restored tab is correct");
+  Assert.equal(tabsRestoredLabels[2], indexedTabLabels[0], "Third restored tab is correct");
+  Assert.equal(tabsRestoredLabels[3], indexedTabLabels[3], "Last restored tab should be a minimized window");
+
+  await promiseAllButPrimaryWindowClosed();
+});
--- a/browser/components/sessionstore/test/browser_speculative_connect.js
+++ b/browser/components/sessionstore/test/browser_speculative_connect.js
@@ -24,43 +24,47 @@ add_task(async function speculative_conn
   await openTabs(win);
 
   // Close the window.
   await BrowserTestUtils.closeWindow(win);
 
   // Reopen a window.
   let newWin = undoCloseWindow(0);
   // Make sure we wait until this window is restored.
-  await BrowserTestUtils.waitForEvent(newWin, "load");
-  await BrowserTestUtils.waitForEvent(newWin.gBrowser.tabContainer, "SSTabRestored");
+  await promiseWindowRestored(newWin);
 
   let tabs = newWin.gBrowser.tabs;
   is(tabs.length, TEST_URLS.length + 1, "Restored right number of tabs");
 
   let e = new MouseEvent("mouseover");
 
   // First tab should be ignored, since it's the default blank tab when we open a new window.
 
   // Trigger a mouse enter on second tab.
   tabs[1].dispatchEvent(e);
-  is(tabs[1].__test_connection_prepared, false, "Second tab doesn't have a connection prepared");
+  ok(!tabs[1].__test_connection_prepared, "Second tab doesn't have a connection prepared");
   is(tabs[1].__test_connection_url, TEST_URLS[0], "Second tab has correct url");
-  is(tabs[1].__SS_connectionPrepared, true, "Second tab should have __SS_connectionPrepared flag after hovered");
+  ok(SessionStore.getLazyTabValue(tabs[1], "connectionPrepared"),
+    "Second tab should have connectionPrepared flag after hovered");
 
   // Trigger a mouse enter on third tab.
   tabs[2].dispatchEvent(e);
-  is(tabs[2].__test_connection_prepared, true, "Third tab has a connection prepared");
+  ok(tabs[2].__test_connection_prepared, "Third tab has a connection prepared");
   is(tabs[2].__test_connection_url, TEST_URLS[1], "Third tab has correct url");
-  is(tabs[2].__SS_connectionPrepared, true, "Third tab should have __SS_connectionPrepared flag after hovered");
+  ok(SessionStore.getLazyTabValue(tabs[2], "connectionPrepared"),
+    "Third tab should have connectionPrepared flag after hovered");
 
   // Last tab is the previously selected tab.
   tabs[3].dispatchEvent(e);
-  is(tabs[3].__SS_connectionPrepared, undefined, "Previous selected tab shouldn't have __SS_connectionPrepared flag");
-  is(tabs[3].__test_connection_prepared, undefined, "Previous selected tab should not have a connection prepared");
-  is(tabs[3].__test_connection_url, undefined, "Previous selected tab should not have a connection prepared");
+  is(SessionStore.getLazyTabValue(tabs[3], "connectionPrepared"), undefined,
+    "Previous selected tab shouldn't have connectionPrepared flag");
+  is(tabs[3].__test_connection_prepared, undefined,
+    "Previous selected tab should not have a connection prepared");
+  is(tabs[3].__test_connection_url, undefined,
+    "Previous selected tab should not have a connection prepared");
 
   await BrowserTestUtils.closeWindow(newWin);
 });
 
 add_task(async function speculative_connect_restore_automatically() {
   Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
   is(Services.prefs.getBoolPref("browser.sessionstore.restore_on_demand"), false, "We're restoring automatically");
   forgetClosedWindows();
@@ -70,30 +74,29 @@ add_task(async function speculative_conn
   await openTabs(win);
 
   // Close the window.
   await BrowserTestUtils.closeWindow(win);
 
   // Reopen a window.
   let newWin = undoCloseWindow(0);
   // Make sure we wait until this window is restored.
-  await BrowserTestUtils.waitForEvent(newWin, "load");
-  await BrowserTestUtils.waitForEvent(newWin.gBrowser.tabContainer, "SSTabRestored");
+  await promiseWindowRestored(newWin);
 
   let tabs = newWin.gBrowser.tabs;
   is(tabs.length, TEST_URLS.length + 1, "Restored right number of tabs");
 
   // First tab is ignored, since it's the default tab open when we open new window
 
   // Second tab.
-  is(tabs[1].__test_connection_prepared, false, "Second tab doesn't have a connection prepared");
+  ok(!tabs[1].__test_connection_prepared, "Second tab doesn't have a connection prepared");
   is(tabs[1].__test_connection_url, TEST_URLS[0], "Second tab has correct host url");
 
   // Third tab.
-  is(tabs[2].__test_connection_prepared, true, "Third tab has a connection prepared");
+  ok(tabs[2].__test_connection_prepared, "Third tab has a connection prepared");
   is(tabs[2].__test_connection_url, TEST_URLS[1], "Third tab has correct host url");
 
   // Last tab is the previously selected tab.
   is(tabs[3].__test_connection_prepared, undefined, "Selected tab should not have a connection prepared");
   is(tabs[3].__test_connection_url, undefined, "Selected tab should not have a connection prepared");
 
   await BrowserTestUtils.closeWindow(newWin);
 });
--- a/browser/components/sessionstore/test/browser_windowStateContainer.js
+++ b/browser/components/sessionstore/test/browser_windowStateContainer.js
@@ -36,17 +36,17 @@ add_task(async function() {
   // Create tabs with different userContextId, but this time we create them with
   // fewer tabs and with different order with win.
   for (let userContextId = 3; userContextId > 0; userContextId--) {
     let tab = win2.gBrowser.addTab("http://example.com/", {userContextId});
     await promiseBrowserLoaded(tab.linkedBrowser);
     await TabStateFlusher.flush(tab.linkedBrowser);
   }
 
-  ss.setWindowState(win2, JSON.stringify(winState), true);
+  await setWindowState(win2, winState, true);
 
   for (let i = 0; i < 4; i++) {
     let browser = win2.gBrowser.tabs[i].linkedBrowser;
     await ContentTask.spawn(browser, { expectedId: i + 1 }, async function(args) {
       Assert.equal(docShell.getOriginAttributes().userContextId,
                    args.expectedId,
                    "The docShell has the correct userContextId");
 
@@ -96,17 +96,17 @@ add_task(async function() {
   await promiseBrowserLoaded(tab2.linkedBrowser);
   await TabStateFlusher.flush(tab2.linkedBrowser);
 
   // Move the first normal tab to end, so the first tab of win2 will be a
   // container tab.
   win2.gBrowser.moveTabTo(win2.gBrowser.tabs[0], win2.gBrowser.tabs.length - 1);
   await TabStateFlusher.flush(win2.gBrowser.tabs[0].linkedBrowser);
 
-  ss.setWindowState(win2, JSON.stringify(winState), true);
+  await setWindowState(win2, winState, true);
 
   for (let i = 0; i < 2; i++) {
     let browser = win2.gBrowser.tabs[i].linkedBrowser;
     await ContentTask.spawn(browser, { expectedId: i }, async function(args) {
       Assert.equal(docShell.getOriginAttributes().userContextId,
                    args.expectedId,
                    "The docShell has the correct userContextId");
 
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -176,16 +176,30 @@ function promiseTabState(tab, state) {
     state = JSON.stringify(state);
   }
 
   let promise = promiseTabRestored(tab);
   ss.setTabState(tab, state);
   return promise;
 }
 
+function promiseWindowRestored(win) {
+  return new Promise(resolve => win.addEventListener("SSWindowRestored", resolve, {once: true}));
+}
+
+async function setBrowserState(state, win = window) {
+  ss.setBrowserState(typeof state != "string" ? JSON.stringify(state) : state);
+  await promiseWindowRestored(win);
+}
+
+async function setWindowState(win, state, overwrite = false) {
+  ss.setWindowState(win, typeof state != "string" ? JSON.stringify(state) : state, overwrite);
+  await promiseWindowRestored(win);
+}
+
 /**
  * Wait for a content -> chrome message.
  */
 function promiseContentMessage(browser, name) {
   let mm = browser.messageManager;
 
   return new Promise(resolve => {
     function removeListener() {
@@ -359,33 +373,34 @@ var gProgressListener = {
     }
   },
 
   observe(browser, topic, data) {
     gProgressListener.onRestored(browser);
   },
 
   onRestored(browser) {
-    if (browser.__SS_restoreState == TAB_STATE_RESTORING) {
+    if (ss.getInternalObjectState(browser) == TAB_STATE_RESTORING) {
       let args = [browser].concat(gProgressListener._countTabs());
       gProgressListener._callback.apply(gProgressListener, args);
     }
   },
 
   _countTabs() {
     let needsRestore = 0, isRestoring = 0, wasRestored = 0;
 
     for (let win of BrowserWindowIterator()) {
       for (let i = 0; i < win.gBrowser.tabs.length; i++) {
         let browser = win.gBrowser.tabs[i].linkedBrowser;
-        if (browser.isConnected && !browser.__SS_restoreState)
+        let state = ss.getInternalObjectState(browser);
+        if (browser.isConnected && !state)
           wasRestored++;
-        else if (browser.__SS_restoreState == TAB_STATE_RESTORING)
+        else if (state == TAB_STATE_RESTORING)
           isRestoring++;
-        else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE || !browser.isConnected)
+        else if (state == TAB_STATE_NEEDS_RESTORE || !browser.isConnected)
           needsRestore++;
       }
     }
     return [needsRestore, isRestoring, wasRestored];
   }
 };
 
 registerCleanupFunction(function() {
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -69,18 +69,18 @@ var Translation = {
     trUI.showURLBarIcon();
 
     if (trUI.shouldShowInfoBar(aBrowser.currentURI))
       trUI.showTranslationInfoBar();
   },
 
   openProviderAttribution() {
     let attribution = this.supportedEngines[this.translationEngine];
-    ChromeUtils.import("resource:///modules/RecentWindow.jsm");
-    RecentWindow.getMostRecentBrowserWindow().openTrustedLinkIn(attribution, "tab");
+    ChromeUtils.import("resource:///modules/BrowserWindowTracker.jsm");
+    BrowserWindowTracker.getTopWindow().openUILinkIn(attribution, "tab");
   },
 
   /**
    * The list of translation engines and their attributions.
    */
   supportedEngines: {
     "bing": "http://aka.ms/MicrosoftTranslatorAttribution",
     "yandex": "http://translate.yandex.com/"
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -31,24 +31,22 @@
 // constructor via a backstage pass.
 var EXPORTED_SYMBOLS = ["formAutofillParent"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "FormAutofillPreferences",
-                               "resource://formautofill/FormAutofillPreferences.jsm");
-ChromeUtils.defineModuleGetter(this, "FormAutofillDoorhanger",
-                               "resource://formautofill/FormAutofillDoorhanger.jsm");
-ChromeUtils.defineModuleGetter(this, "MasterPassword",
-                               "resource://formautofill/MasterPassword.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  FormAutofillPreferences: "resource://formautofill/FormAutofillPreferences.jsm",
+  FormAutofillDoorhanger: "resource://formautofill/FormAutofillDoorhanger.jsm",
+  MasterPassword: "resource://formautofill/MasterPassword.jsm",
+});
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]);
 
 const {
   ENABLED_AUTOFILL_ADDRESSES_PREF,
   ENABLED_AUTOFILL_CREDITCARDS_PREF,
   CREDITCARDS_COLLECTION_NAME,
@@ -238,17 +236,17 @@ FormAutofillParent.prototype = {
         data.guids.forEach(guid => this.formAutofillStorage.creditCards.remove(guid));
         break;
       }
       case "FormAutofill:OnFormSubmit": {
         this._onFormSubmit(data, target);
         break;
       }
       case "FormAutofill:OpenPreferences": {
-        const win = RecentWindow.getMostRecentBrowserWindow();
+        const win = BrowserWindowTracker.getTopWindow();
         win.openPreferences("panePrivacy", {origin: "autofillFooter"});
         break;
       }
       case "FormAutofill:GetDecryptedString": {
         let {cipherText, reauth} = data;
         let string;
         try {
           string = await MasterPassword.decrypt(cipherText, reauth);
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -17,18 +17,16 @@ ChromeUtils.defineModuleGetter(this, "Ad
 ChromeUtils.defineModuleGetter(this, "BrowserUtils",
                                "resource://gre/modules/BrowserUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "PageActions",
                                "resource:///modules/PageActions.jsm");
 ChromeUtils.defineModuleGetter(this, "Pocket",
                                "chrome://pocket/content/Pocket.jsm");
 ChromeUtils.defineModuleGetter(this, "ReaderMode",
                                "resource://gre/modules/ReaderMode.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGetter(this, "gPocketBundle", function() {
   return Services.strings.createBundle("chrome://pocket/locale/pocket.properties");
 });
 XPCOMUtils.defineLazyGetter(this, "gPocketStyleURI", function() {
   return Services.io.newURI("chrome://pocket-shared/skin/pocket.css");
 });
--- a/browser/locales/en-US/chrome/browser-region/region.properties
+++ b/browser/locales/en-US/chrome/browser-region/region.properties
@@ -2,18 +2,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # Default search engine
 browser.search.defaultenginename=Google
 
 # Search engine order (order displayed in the search bar dropdown)s
 browser.search.order.1=Google
-browser.search.order.2=Yahoo
-browser.search.order.3=Bing
+browser.search.order.2=Bing
 
 # This is the default set of web based feed handlers shown in the reader
 # selection UI
 browser.contentHandlers.types.0.title=My Yahoo!
 browser.contentHandlers.types.0.uri=https://add.my.yahoo.com/rss?url=%s
 
 # increment this number when anything gets changed in the list below.  This will
 # cause Firefox to re-read these prefs and inject any new handlers into the 
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -65,17 +65,16 @@
     locale/browser/searchplugins/list.json      (search/list.json)
 #else
     locale/browser/searchplugins/               (.deps/generated_@AB_CD@/*.xml)
     locale/browser/searchplugins/list.json      (.deps/generated_@AB_CD@/list.json)
 #endif
     locale/browser/searchplugins/images/amazon.ico     (searchplugins/images/amazon.ico)
     locale/browser/searchplugins/images/ebay.ico       (searchplugins/images/ebay.ico)
     locale/browser/searchplugins/images/wikipedia.ico  (searchplugins/images/wikipedia.ico)
-    locale/browser/searchplugins/images/yahoo.ico      (searchplugins/images/yahoo.ico)
     locale/browser/searchplugins/images/yandex-en.ico  (searchplugins/images/yandex-en.ico)
     locale/browser/searchplugins/images/yandex-ru.ico  (searchplugins/images/yandex-ru.ico)
 % locale browser-region @AB_CD@ %locale/browser-region/
     locale/browser-region/region.properties        (%chrome/browser-region/region.properties)
 # the following files are browser-specific overrides
     locale/browser/netError.dtd                (%chrome/overrides/netError.dtd)
     locale/browser/appstrings.properties       (%chrome/overrides/appstrings.properties)
     locale/browser/downloads/settingsChange.dtd  (%chrome/overrides/settingsChange.dtd)
--- a/browser/locales/l10n-changesets.json
+++ b/browser/locales/l10n-changesets.json
@@ -219,16 +219,31 @@
             "macosx64-devedition", 
             "win32", 
             "win32-devedition", 
             "win64", 
             "win64-devedition"
         ], 
         "revision": "default"
     }, 
+    "crh": {
+        "platforms": [
+            "linux", 
+            "linux-devedition", 
+            "linux64", 
+            "linux64-devedition", 
+            "macosx64", 
+            "macosx64-devedition", 
+            "win32", 
+            "win32-devedition", 
+            "win64", 
+            "win64-devedition"
+        ], 
+        "revision": "default"
+    }, 
     "cs": {
         "platforms": [
             "linux", 
             "linux-devedition", 
             "linux64", 
             "linux64-devedition", 
             "macosx64", 
             "macosx64-devedition", 
deleted file mode 100644
index 9bd1d9f7c008c3f288c759f70b42a6f32bf55211..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-NO.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Søk</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://no.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://no.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://no.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-answer-zh-TW.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo!奇摩知識+</ShortName>
-<Description>Yahoo!奇摩知識+</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">data:image/gif;base64,R0lGODlhEAAQAOYAAAAAAP///3dub4eFh15ZYGZXcWxlgltdakxQXF1ulFJWW1VZXlpeY1xgZWZ2iGtucU5WXVFZYFJYXQB1xwB1xgByw1p5jACd9ACH0wCEzACDywB/ycDo/sfr/8rq/FhhZlhfY7O6vgCb6ACQ1wCJzgCHzaTd+qfe+rXk+7nl+8fs/9Lw/9XZ2+bo6Y3W+I/Y+Z3b+Z3c+Z7c+aDc+aXi/qTe+qfh+6rg+rjj+Lzn+8Pq/Mjs/QCi4wCZ2AWc2R2l3Uy56Fa+6Yjc/ojU9YvX+JDa+JLZ+JXb+Z7c9qrk/Mrt/FzB6L7u/8bd5fP9/+j8/+D8/+b+/+X//+f//+r//+////f//4R/df2bAO6lLf/+/Yh0boNuanBnZq6urv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAF8ALAAAAAAQABAAAAeggF+Cgl4IEREHXoOLghAtAZABLR+MghFaAU5KHiuQDIxejzs3MjAmKFItioMQAVEpNUYuRycdAQuLrVUqQzQvRTM5AQq5AVMVJEBESTY6ARKLoQEZPyUbQRxQqowQVj44SxpIVAEPlRBPPQEUMQEsDZVfXU0YE0wBAwXxX1w8I0IBQOz74mCLiAshCAwkiMWABQELGWa54iDilwQOKg4MBAA7</Image>
-<Url type="text/html" method="GET" template="https://tw.knowledge.search.yahoo.com/search" resultDomain="tw.knowledge.search.yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="fr" value="uh3_answers_web_gs"/>
-</Url>
-<SearchForm>https://tw.answers.yahoo.com/</SearchForm>
-</SearchPlugin>
-
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-ar.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo Argentina</ShortName>
-<Description>Buscar en Yahoo Argentina</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://ar.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://ar.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://ar.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-bid-zh-TW.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo!奇摩拍賣</ShortName>
-<Description>Yahoo!奇摩拍賣</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">data:image/gif;base64,R0lGODlhEAAQAMQfAP+7Q4aWvf2QBaKtx//MS+6OLI1MMf/XYv+sKkpalEZNepyUa4KOo2ttjpx4Tdh1ALdrI9lSAHhaVWNSa+Z7Ge9zAPdrALVaIR4/ie9rAOZrELXB0Gtzc/9zCJg+Hf///yH5BAEAAB8ALAAAAAAQABAAAAWM4CeO3xAEA6mKw3IQDrOSAUEAiBPMpXsciIdk1XAUfkCBwKBiAG4IAED5UKg4iGxFWfFgNk0lF+KJWBqribgCwUgKlsRok0hMHvj2YNKJfzYMBxcXE4UTEQkbChlxDT9SBZEUGogBiwkSBFE4khoGcgEJAw0GEBSRBRQUF580CQawsbAJO64Kt7i0KiEAOw==</Image>
-<Url type="text/html" method="GET" template="http://tw.search.bid.yahoo.com/search/ac" resultdomain="bid.yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-</Url>
-<SearchForm>http://tw.search.bid.yahoo.com/search/ac</SearchForm>
-</SearchPlugin>
\ No newline at end of file
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-br.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Pesquisa Yahoo</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://br.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://br.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://br.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-ch.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Tschertga Yahoo Svizra</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://ch.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://ch.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://ch.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-cl.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Buscar en Yahoo Chile</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://cl.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://cl.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://cl.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-de.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://de.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://de.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://de.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-en-CA.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo Canada</ShortName>
-<Description>Yahoo Canada Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://ca.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://ca.search.yahoo.com/yhs/search"
-     resultdomain="yahoo.com" rel="searchform">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="hspart" value="mozilla"/>
-  <MozParam name="hsimp" condition="purpose" purpose="searchbar"   value="yhs-001"/>
-  <MozParam name="hsimp" condition="purpose" purpose="keyword"     value="yhs-002"/>
-  <MozParam name="hsimp" condition="purpose" purpose="homepage"    value="yhs-003"/>
-  <MozParam name="hsimp" condition="purpose" purpose="newtab"      value="yhs-004"/>
-  <MozParam name="hsimp" condition="purpose" purpose="contextmenu" value="yhs-005"/>
-  <MozParam name="hsimp" condition="purpose" purpose="system"      value="yhs-007"/>
-</Url>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-en-GB.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo.co.uk</ShortName>
-<Description>Yahoo UK &amp; Ireland Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://uk.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://uk.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://uk.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-es.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://es.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://es.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://es.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-espanol.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://espanol.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://espanol.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://espanol.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-fi.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo-haku</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://fi.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://fi.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://fi.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-france.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Recherche Yahoo</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://fr.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://fr.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://fr.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-fy-NL.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Sykje mei Yahoo</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://nl.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://nl.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://nl.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-id.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Pencarian Yahoo</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://id.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://id.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://id.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-in.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://in.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://in.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://in.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-it.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://it.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://it.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://it.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-mx.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://mx.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://mx.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://mx.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-sv-SE.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Sök</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://se.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://se.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://se.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-tl.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://sg.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://sg.search.yahoo.com/search" resultdomain="yahoo.com">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="fr" value="moz35" />
-</Url>
-<SearchForm>https://sg.search.yahoo.com/</SearchForm>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-zh-TW-HK.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo 雅虎香港</ShortName>
-<Description>Yahoo 雅虎香港</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://hk.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://hk.search.yahoo.com/yhs/search"
-     resultdomain="yahoo.com" rel="searchform">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="hspart" value="mozilla"/>
-  <MozParam name="hsimp" condition="purpose" purpose="searchbar"   value="yhs-001"/>
-  <MozParam name="hsimp" condition="purpose" purpose="keyword"     value="yhs-002"/>
-  <MozParam name="hsimp" condition="purpose" purpose="homepage"    value="yhs-003"/>
-  <MozParam name="hsimp" condition="purpose" purpose="newtab"      value="yhs-004"/>
-  <MozParam name="hsimp" condition="purpose" purpose="contextmenu" value="yhs-005"/>
-</Url>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo-zh-TW.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo 搜尋</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://tw.search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://tw.search.yahoo.com/yhs/search"
-     resultdomain="yahoo.com" rel="searchform">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="hspart" value="mozilla"/>
-  <MozParam name="hsimp" condition="purpose" purpose="searchbar"   value="yhs-001"/>
-  <MozParam name="hsimp" condition="purpose" purpose="keyword"     value="yhs-002"/>
-  <MozParam name="hsimp" condition="purpose" purpose="homepage"    value="yhs-003"/>
-  <MozParam name="hsimp" condition="purpose" purpose="newtab"      value="yhs-004"/>
-  <MozParam name="hsimp" condition="purpose" purpose="contextmenu" value="yhs-005"/>
-</Url>
-</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yahoo.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Yahoo</ShortName>
-<Description>Yahoo Search</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yahoo.ico</Image>
-<Url type="application/x-suggestions+json" method="GET"
-     template="https://search.yahoo.com/sugg/ff">
-  <Param name="output"  value="fxjson" />
-  <Param name="appid"   value="ffd" />
-  <Param name="command" value="{searchTerms}" />
-</Url>
-<Url type="text/html" method="GET" template="https://search.yahoo.com/yhs/search"
-     resultdomain="yahoo.com" rel="searchform">
-  <Param name="p" value="{searchTerms}"/>
-  <Param name="ei" value="UTF-8"/>
-  <Param name="hspart" value="mozilla"/>
-  <MozParam name="hsimp" condition="purpose" purpose="searchbar"   value="yhs-001"/>
-  <MozParam name="hsimp" condition="purpose" purpose="keyword"     value="yhs-002"/>
-  <MozParam name="hsimp" condition="purpose" purpose="homepage"    value="yhs-003"/>
-  <MozParam name="hsimp" condition="purpose" purpose="newtab"      value="yhs-004"/>
-  <MozParam name="hsimp" condition="purpose" purpose="contextmenu" value="yhs-005"/>
-  <MozParam name="hsimp" condition="purpose" purpose="system"      value="yhs-007"/>
-</Url>
-</SearchPlugin>
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -4,24 +4,22 @@
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["BrowserUITelemetry"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "AppConstants",
-  "resource://gre/modules/AppConstants.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-  "resource:///modules/RecentWindow.jsm");
-ChromeUtils.defineModuleGetter(this, "CustomizableUI",
-  "resource:///modules/CustomizableUI.jsm");
-ChromeUtils.defineModuleGetter(this, "UITour",
-  "resource:///modules/UITour.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  AppConstants: "resource://gre/modules/AppConstants.jsm",
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  CustomizableUI: "resource:///modules/CustomizableUI.jsm",
+  UITour: "resource:///modules/UITour.jsm",
+});
 XPCOMUtils.defineLazyGetter(this, "Timer", function() {
   let timer = {};
   ChromeUtils.import("resource://gre/modules/Timer.jsm", timer);
   return timer;
 });
 
 const MS_SECOND = 1000;
 const MS_MINUTE = MS_SECOND * 60;
@@ -261,17 +259,17 @@ var BrowserUITelemetry = {
   _firstWindowMeasurements: null,
   _gatherFirstWindowMeasurements() {
     // We'll gather measurements as soon as the session has restored.
     // We do this here instead of waiting for UITelemetry to ask for
     // our measurements because at that point all browser windows have
     // probably been closed, since the vast majority of saved-session
     // pings are gathered during shutdown.
     Services.search.init(rv => {
-      let win = RecentWindow.getMostRecentBrowserWindow({
+      let win = BrowserWindowTracker.getTopWindow({
         private: false,
         allowPopups: false,
       });
       // If there are no such windows, we're out of luck. :(
       this._firstWindowMeasurements = win ? this._getWindowMeasurements(win, rv)
                                           : {};
     });
   },
rename from browser/modules/UpdateTopLevelContentWindowIDHelper.jsm
rename to browser/modules/BrowserWindowTracker.jsm
--- a/browser/modules/UpdateTopLevelContentWindowIDHelper.jsm
+++ b/browser/modules/BrowserWindowTracker.jsm
@@ -2,137 +2,221 @@
  * 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/. */
 
 /*
  * This module tracks each browser window and informs network module
  * the current selected tab's content outer window ID.
  */
 
-var EXPORTED_SYMBOLS = ["trackBrowserWindow"];
+var EXPORTED_SYMBOLS = ["BrowserWindowTracker"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 // Lazy getters
-XPCOMUtils.defineLazyServiceGetter(this, "_focusManager",
-                                   "@mozilla.org/focus-manager;1",
-                                   "nsIFocusManager");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  AppConstants: "resource://gre/modules/AppConstants.jsm",
+  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm"
+});
 
 // Constants
 const TAB_EVENTS = ["TabBrowserInserted", "TabSelect"];
-const WINDOW_EVENTS = ["activate", "unload"];
+const WINDOW_EVENTS = ["activate", "sizemodechange", "unload"];
 const DEBUG = false;
 
 // Variables
-var _lastFocusedWindow = null;
 var _lastTopLevelWindowID = 0;
-
-// Exported symbol
-function trackBrowserWindow(aWindow) {
-  WindowHelper.addWindow(aWindow);
-}
+var _trackedWindows = [];
 
 // Global methods
 function debug(s) {
   if (DEBUG) {
     dump("-*- UpdateTopLevelContentWindowIDHelper: " + s + "\n");
   }
 }
 
-function _updateCurrentContentOuterWindowID(aBrowser) {
-  if (!aBrowser.outerWindowID ||
-      aBrowser.outerWindowID === _lastTopLevelWindowID) {
+function _updateCurrentContentOuterWindowID(browser) {
+  if (!browser.outerWindowID ||
+      browser.outerWindowID === _lastTopLevelWindowID) {
     return;
   }
 
-  debug("Current window uri=" + aBrowser.currentURI.spec +
-        " id=" + aBrowser.outerWindowID);
+  debug("Current window uri=" + browser.currentURI.spec +
+        " id=" + browser.outerWindowID);
 
-  _lastTopLevelWindowID = aBrowser.outerWindowID;
+  _lastTopLevelWindowID = browser.outerWindowID;
   let windowIDWrapper = Cc["@mozilla.org/supports-PRUint64;1"]
                           .createInstance(Ci.nsISupportsPRUint64);
   windowIDWrapper.data = _lastTopLevelWindowID;
   Services.obs.notifyObservers(windowIDWrapper,
                                "net:current-toplevel-outer-content-windowid");
 }
 
-function _handleEvent(aEvent) {
-  switch (aEvent.type) {
+function _handleEvent(event) {
+  switch (event.type) {
     case "TabBrowserInserted":
-      if (aEvent.target.ownerGlobal.gBrowser.selectedBrowser === aEvent.target.linkedBrowser) {
-        _updateCurrentContentOuterWindowID(aEvent.target.linkedBrowser);
+      if (event.target.ownerGlobal.gBrowser.selectedBrowser === event.target.linkedBrowser) {
+        _updateCurrentContentOuterWindowID(event.target.linkedBrowser);
       }
       break;
     case "TabSelect":
-      _updateCurrentContentOuterWindowID(aEvent.target.linkedBrowser);
+      _updateCurrentContentOuterWindowID(event.target.linkedBrowser);
       break;
     case "activate":
-      WindowHelper.onActivate(aEvent.target);
+      WindowHelper.onActivate(event.target);
+      break;
+    case "sizemodechange":
+      WindowHelper.onSizemodeChange(event.target);
       break;
     case "unload":
-      WindowHelper.removeWindow(aEvent.currentTarget);
+      WindowHelper.removeWindow(event.currentTarget);
       break;
   }
 }
 
-function _handleMessage(aMessage) {
-  let browser = aMessage.target;
-  if (aMessage.name === "Browser:Init" &&
+function _handleMessage(message) {
+  let browser = message.target;
+  if (message.name === "Browser:Init" &&
       browser === browser.ownerGlobal.gBrowser.selectedBrowser) {
     _updateCurrentContentOuterWindowID(browser);
   }
 }
 
+function _trackWindowOrder(window) {
+  _trackedWindows.splice(window.windowState == window.STATE_MINIMIZED ?
+    _trackedWindows.length - 1 : 0, 0, window);
+}
+
+function _untrackWindowOrder(window) {
+  let idx = _trackedWindows.indexOf(window);
+  if (idx >= 0)
+    _trackedWindows.splice(idx, 1);
+}
+
 // Methods that impact a window. Put into single object for organization.
 var WindowHelper = {
-  addWindow: function NP_WH_addWindow(aWindow) {
+  addWindow(window) {
     // Add event listeners
     TAB_EVENTS.forEach(function(event) {
-      aWindow.gBrowser.tabContainer.addEventListener(event, _handleEvent);
+      window.gBrowser.tabContainer.addEventListener(event, _handleEvent);
     });
     WINDOW_EVENTS.forEach(function(event) {
-      aWindow.addEventListener(event, _handleEvent);
+      window.addEventListener(event, _handleEvent);
     });
 
-    let messageManager = aWindow.getGroupMessageManager("browsers");
+    let messageManager = window.getGroupMessageManager("browsers");
     messageManager.addMessageListener("Browser:Init", _handleMessage);
 
-    // This gets called AFTER activate event, so if this is the focused window
-    // we want to activate it.
-    if (aWindow == _focusManager.activeWindow)
-      this.handleFocusedWindow(aWindow);
+    _trackWindowOrder(window);
 
     // Update the selected tab's content outer window ID.
-    _updateCurrentContentOuterWindowID(aWindow.gBrowser.selectedBrowser);
+    _updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser);
   },
 
-  removeWindow: function NP_WH_removeWindow(aWindow) {
-    if (aWindow == _lastFocusedWindow)
-      _lastFocusedWindow = null;
+  removeWindow(window) {
+    _untrackWindowOrder(window);
 
     // Remove the event listeners
     TAB_EVENTS.forEach(function(event) {
-      aWindow.gBrowser.tabContainer.removeEventListener(event, _handleEvent);
+      window.gBrowser.tabContainer.removeEventListener(event, _handleEvent);
     });
     WINDOW_EVENTS.forEach(function(event) {
-      aWindow.removeEventListener(event, _handleEvent);
+      window.removeEventListener(event, _handleEvent);
     });
 
-    let messageManager = aWindow.getGroupMessageManager("browsers");
+    let messageManager = window.getGroupMessageManager("browsers");
     messageManager.removeMessageListener("Browser:Init", _handleMessage);
   },
 
-  onActivate: function NP_WH_onActivate(aWindow, aHasFocus) {
+  onActivate(window, hasFocus) {
     // If this window was the last focused window, we don't need to do anything
-    if (aWindow == _lastFocusedWindow)
+    if (window == _trackedWindows[0])
       return;
 
-    this.handleFocusedWindow(aWindow);
+    _untrackWindowOrder(window);
+    _trackWindowOrder(window);
+
+    _updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser);
+  },
 
-    _updateCurrentContentOuterWindowID(aWindow.gBrowser.selectedBrowser);
+  onSizemodeChange(window) {
+    if (window.windowState == window.STATE_MINIMIZED) {
+      // Make sure to have the minimized window at the end of the list.
+      _untrackWindowOrder(window);
+      _trackedWindows.push(window);
+    }
   },
 
-  handleFocusedWindow: function NP_WH_handleFocusedWindow(aWindow) {
-    // aWindow is now focused
-    _lastFocusedWindow = aWindow;
+  getTopWindow(options) {
+    let checkPrivacy = typeof options == "object" &&
+                       "private" in options;
+
+    let allowPopups = typeof options == "object" && !!options.allowPopups;
+
+    function isSuitableBrowserWindow(win) {
+      return (!win.closed &&
+              (allowPopups || win.toolbar.visible) &&
+              (!checkPrivacy ||
+               PrivateBrowsingUtils.permanentPrivateBrowsing ||
+               PrivateBrowsingUtils.isWindowPrivate(win) == options.private));
+    }
+
+    let broken_wm_z_order =
+      AppConstants.platform != "macosx" && AppConstants.platform != "win";
+
+    if (broken_wm_z_order) {
+      let win = Services.wm.getMostRecentWindow("navigator:browser");
+
+      // if we're lucky, this isn't a popup, and we can just return this
+      if (win && !isSuitableBrowserWindow(win)) {
+        win = null;
+        let windowList = Services.wm.getEnumerator("navigator:browser");
+        // this is oldest to newest, so this gets a bit ugly
+        while (windowList.hasMoreElements()) {
+          let nextWin = windowList.getNext();
+          if (isSuitableBrowserWindow(nextWin))
+            win = nextWin;
+        }
+      }
+      return win;
+    }
+    let windowList = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
+    while (windowList.hasMoreElements()) {
+      let win = windowList.getNext();
+      if (isSuitableBrowserWindow(win))
+        return win;
+    }
+    return null;
+  }
+};
+
+this.BrowserWindowTracker = {
+  /**
+   * Get the most recent browser window.
+   *
+   * @param options an object accepting the arguments for the search.
+   *        * private: true to restrict the search to private windows
+   *            only, false to restrict the search to non-private only.
+   *            Omit the property to search in both groups.
+   *        * allowPopups: true if popup windows are permissable.
+   */
+  getTopWindow(options) {
+    return WindowHelper.getTopWindow(options);
   },
+
+  /**
+   * Iterator property that yields window objects by z-index, in reverse order.
+   * This means that the lastly focused window will the first item that is yielded.
+   * Note: we only know the order of windows we're actively tracking, which
+   * basically means _only_ browser windows.
+   */
+  orderedWindows: {
+    * [Symbol.iterator]() {
+      for (let window of _trackedWindows)
+        yield window;
+    }
+  },
+
+  track(window) {
+    return WindowHelper.addWindow(window);
+  }
 };
--- a/browser/modules/ContentCrashHandlers.jsm
+++ b/browser/modules/ContentCrashHandlers.jsm
@@ -6,32 +6,26 @@
 
 var EXPORTED_SYMBOLS = [ "TabCrashHandler",
                          "PluginCrashReporter",
                          "UnsubmittedCrashHandler" ];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-ChromeUtils.defineModuleGetter(this, "CrashSubmit",
-  "resource://gre/modules/CrashSubmit.jsm");
-ChromeUtils.defineModuleGetter(this, "AppConstants",
-  "resource://gre/modules/AppConstants.jsm");
-ChromeUtils.defineModuleGetter(this, "RemotePages",
-  "resource://gre/modules/RemotePageManager.jsm");
-ChromeUtils.defineModuleGetter(this, "SessionStore",
-  "resource:///modules/sessionstore/SessionStore.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-  "resource:///modules/RecentWindow.jsm");
-ChromeUtils.defineModuleGetter(this, "PluralForm",
-  "resource://gre/modules/PluralForm.jsm");
-ChromeUtils.defineModuleGetter(this, "setTimeout",
-  "resource://gre/modules/Timer.jsm");
-ChromeUtils.defineModuleGetter(this, "clearTimeout",
-  "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  AppConstants: "resource://gre/modules/AppConstants.jsm",
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  clearTimeout: "resource://gre/modules/Timer.jsm",
+  CrashSubmit: "resource://gre/modules/CrashSubmit.jsm",
+  PluralForm: "resource://gre/modules/PluralForm.jsm",
+  RemotePages: "resource://gre/modules/RemotePageManager.jsm",
+  SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
+  setTimeout: "resource://gre/modules/Timer.jsm"
+});
 
 XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() {
   const url = "chrome://browser/locale/browser.properties";
   return Services.strings.createBundle(url);
 });
 
 // We don't process crash reports older than 28 days, so don't bother
 // submitting them
@@ -833,17 +827,17 @@ var UnsubmittedCrashHandler = {
    *        onAction (function, optional)
    *          A callback to fire once the user performs an
    *          action on the notification bar (this includes
    *          dismissing the notification).
    *
    * @returns The <xul:notification> if one is shown. null otherwise.
    */
   show({ notificationID, message, reportIDs, onAction }) {
-    let chromeWin = RecentWindow.getMostRecentBrowserWindow();
+    let chromeWin = BrowserWindowTracker.getTopWindow();
     if (!chromeWin) {
       // Can't show a notification in this case. We'll hopefully
       // get another opportunity to have the user submit their
       // crash reports later.
       return null;
     }
 
     let nb =  chromeWin.document.getElementById("global-notificationbox");
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -334,16 +334,17 @@ var ContentSearch = {
       let iconBuffer = uri;
       if (!uriFlag) {
         iconBuffer = await this._arrayBufferFromDataURI(uri);
       }
       state.engines.push({
         name: engine.name,
         iconBuffer,
         hidden: hiddenList.includes(engine.name),
+        identifier: engine.identifier
       });
     }
     return state;
   },
 
   _processEventQueue() {
     if (this._currentEventPromise || !this._eventQueue.length) {
       return;
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -3,28 +3,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 var EXPORTED_SYMBOLS = ["ExtensionsUI"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/EventEmitter.jsm");
 
-ChromeUtils.defineModuleGetter(this, "AddonManager",
-                               "resource://gre/modules/AddonManager.jsm");
-ChromeUtils.defineModuleGetter(this, "AddonManagerPrivate",
-                               "resource://gre/modules/AddonManager.jsm");
-ChromeUtils.defineModuleGetter(this, "AppMenuNotifications",
-                               "resource://gre/modules/AppMenuNotifications.jsm");
-ChromeUtils.defineModuleGetter(this, "ExtensionData",
-                               "resource://gre/modules/Extension.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
-ChromeUtils.defineModuleGetter(this, "Services",
-                               "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  AddonManager: "resource://gre/modules/AddonManager.jsm",
+  AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
+  AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.jsm",
+  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  ExtensionData: "resource://gre/modules/Extension.jsm",
+  Services: "resource://gre/modules/Services.jsm"
+});
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
                                       "extensions.webextPermissionPrompts", false);
 
 const DEFAULT_EXTENSION_ICON = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 
 const BROWSER_PROPERTIES = "chrome://browser/locale/browser.properties";
 const BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
--- a/browser/modules/Feeds.jsm
+++ b/browser/modules/Feeds.jsm
@@ -6,18 +6,18 @@
 
 var EXPORTED_SYMBOLS = [ "Feeds" ];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "BrowserUtils",
                                "resource://gre/modules/BrowserUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "RecentWindow",
-                               "resource:///modules/RecentWindow.jsm");
+ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
+                               "resource:///modules/BrowserWindowTracker.jsm");
 
 var Feeds = {
   // Listeners are added in nsBrowserGlue.js
   receiveMessage(aMessage) {
     let data = aMessage.data;
     switch (aMessage.name) {
       case "WCCR:registerProtocolHandler": {
         let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
@@ -38,17 +38,17 @@ var Feeds = {
       case "WCCR:setAutoHandler": {
         let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
                           getService(Ci.nsIWebContentConverterService);
         registrar.setAutoHandler(data.contentType, data.handler);
         break;
       }
 
       case "FeedConverter:addLiveBookmark": {
-        let topWindow = RecentWindow.getMostRecentBrowserWindow();
+        let topWindow = BrowserWindowTracker.getTopWindow();
         topWindow.PlacesCommandHook.addLiveBookmark(data.spec, data.title, data.subtitle)
                                    .catch(Cu.reportError);
         break;
       }
     }
   },
 
   /**
deleted file mode 100644
--- a/browser/modules/RecentWindow.jsm
+++ /dev/null
@@ -1,65 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-var EXPORTED_SYMBOLS = ["RecentWindow"];
-
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-var RecentWindow = {
-  /*
-   * Get the most recent browser window.
-   *
-   * @param aOptions an object accepting the arguments for the search.
-   *        * private: true to restrict the search to private windows
-   *            only, false to restrict the search to non-private only.
-   *            Omit the property to search in both groups.
-   *        * allowPopups: true if popup windows are permissable.
-   */
-  getMostRecentBrowserWindow: function RW_getMostRecentBrowserWindow(aOptions) {
-    let checkPrivacy = typeof aOptions == "object" &&
-                       "private" in aOptions;
-
-    let allowPopups = typeof aOptions == "object" && !!aOptions.allowPopups;
-
-    function isSuitableBrowserWindow(win) {
-      return (!win.closed &&
-              (allowPopups || win.toolbar.visible) &&
-              (!checkPrivacy ||
-               PrivateBrowsingUtils.permanentPrivateBrowsing ||
-               PrivateBrowsingUtils.isWindowPrivate(win) == aOptions.private));
-    }
-
-    let broken_wm_z_order =
-      AppConstants.platform != "macosx" && AppConstants.platform != "win";
-
-    if (broken_wm_z_order) {
-      let win = Services.wm.getMostRecentWindow("navigator:browser");
-
-      // if we're lucky, this isn't a popup, and we can just return this
-      if (win && !isSuitableBrowserWindow(win)) {
-        win = null;
-        let windowList = Services.wm.getEnumerator("navigator:browser");
-        // this is oldest to newest, so this gets a bit ugly
-        while (windowList.hasMoreElements()) {
-          let nextWin = windowList.getNext();
-          if (isSuitableBrowserWindow(nextWin))
-            win = nextWin;
-        }
-      }
-      return win;
-    }
-    let windowList = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
-    while (windowList.hasMoreElements()) {
-      let win = windowList.getNext();
-      if (isSuitableBrowserWindow(win))
-        return win;
-    }
-    return null;
-  }
-};
-
--- a/browser/modules/Sanitizer.jsm
+++ b/browser/modules/Sanitizer.jsm
@@ -12,17 +12,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   FormHistory: "resource://gre/modules/FormHistory.jsm",
   Downloads: "resource://gre/modules/Downloads.jsm",
   DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
   TelemetryStopwatch: "resource://gre/modules/TelemetryStopwatch.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
   ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.jsm",
-  OfflineAppCacheHelper: "resource:///modules/offlineAppCache.jsm",
+  OfflineAppCacheHelper: "resource://gre/modules/offlineAppCache.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "sas",
                                    "@mozilla.org/storage/activity-service;1",
                                    "nsIStorageActivityService");
 XPCOMUtils.defineLazyServiceGetter(this, "quotaManagerService",
                                    "@mozilla.org/dom/quota-manager-service;1",
                                    "nsIQuotaManagerService");
@@ -270,16 +270,20 @@ var Sanitizer = {
     }
   },
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsiObserver,
     Ci.nsISupportsWeakReference
   ]),
 
+  // When making any changes to the sanitize implementations here,
+  // please check whether the changes are applicable to Android
+  // (mobile/android/modules/Sanitizer.jsm) as well.
+
   items: {
     cache: {
       async clear(range) {
         let seenException;
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
 
         try {
--- a/browser/modules/SiteDataManager.jsm
+++ b/browser/modules/SiteDataManager.jsm
@@ -1,15 +1,15 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "OfflineAppCacheHelper",
-                               "resource:///modules/offlineAppCache.jsm");
+                               "resource://gre/modules/offlineAppCache.jsm");
 ChromeUtils.defineModuleGetter(this, "ServiceWorkerCleanUp",
                                "resource://gre/modules/ServiceWorkerCleanUp.jsm");
 
 var EXPORTED_SYMBOLS = [
   "SiteDataManager"
 ];
 
 XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -44,16 +44,19 @@ with Files("AboutNewTab.jsm"):
     BUG_COMPONENT = ("Firefox", "New Tab Page")
 
 with Files('AsyncTabSwitcher.jsm'):
     BUG_COMPONENT = ('Firefox', 'Tabbed Browser')
 
 with Files("AttributionCode.jsm"):
     BUG_COMPONENT = ("Toolkit", "Telemetry")
 
+with Files("BrowserWindowTracker.jsm"):
+    BUG_COMPONENT = ("Core", "Networking")
+
 with Files("*Telemetry.jsm"):
     BUG_COMPONENT = ("Toolkit", "Telemetry")
 
 with Files("ContentCrashHandlers.jsm"):
     BUG_COMPONENT = ("Toolkit", "Crash Reporting")
 
 with Files("ContentSearch.jsm"):
     BUG_COMPONENT = ("Firefox", "Search")
@@ -101,22 +104,16 @@ with Files("Windows8WindowFrameColor.jsm
     BUG_COMPONENT = ("Firefox", "Theme")
 
 with Files("WindowsJumpLists.jsm"):
     BUG_COMPONENT = ("Firefox", "Shell Integration")
 
 with Files("WindowsPreviewPerTab.jsm"):
     BUG_COMPONENT = ("Core", "Widget: Win32")
 
-with Files("offlineAppCache.jsm"):
-    BUG_COMPONENT = ("Firefox", "Preferences")
-
-with Files("UpdateTopLevelContentWindowIDHelper.jsm"):
-    BUG_COMPONENT = ("Core", "Networking")
-
 with Files("webrtcUI.jsm"):
     BUG_COMPONENT = ("Firefox", "Device Permissions")
 
 with Files("ZoomUI.jsm"):
     BUG_COMPONENT = ("Firefox", "Toolbars and Customization")
 
 
 BROWSER_CHROME_MANIFESTS += [
@@ -128,46 +125,44 @@ XPCSHELL_TESTS_MANIFESTS += ['test/unit/
 EXTRA_JS_MODULES += [
     'AboutHome.jsm',
     'AboutNewTab.jsm',
     'AsyncTabSwitcher.jsm',
     'AttributionCode.jsm',
     'BrowserErrorReporter.jsm',
     'BrowserUITelemetry.jsm',
     'BrowserUsageTelemetry.jsm',
+    'BrowserWindowTracker.jsm',
     'ContentClick.jsm',
     'ContentCrashHandlers.jsm',
     'ContentLinkHandler.jsm',
     'ContentMetaHandler.jsm',
     'ContentObservers.js',
     'ContentSearch.jsm',
     'ContentWebRTC.jsm',
     'ContextMenu.jsm',
     'ExtensionsUI.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'LaterRun.jsm',
-    'offlineAppCache.jsm',
     'OpenInTabsUtils.jsm',
     'PageActions.jsm',
     'PermissionUI.jsm',
     'PingCentre.jsm',
     'PluginContent.jsm',
     'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
-    'RecentWindow.jsm',
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
     'SchedulePressure.jsm',
     'SiteDataManager.jsm',
     'SitePermissions.jsm',
     'ThemeVariableMap.jsm',
     'TransientPrefs.jsm',
-    'UpdateTopLevelContentWindowIDHelper.jsm',
     'webrtcUI.jsm',
     'ZoomUI.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES += [
         'Windows8WindowFrameColor.jsm',
         'WindowsJumpLists.jsm',
--- a/browser/modules/test/browser/browser.ini
+++ b/browser/modules/test/browser/browser.ini
@@ -9,16 +9,17 @@ support-files =
 skip-if = !e10s # Bug 1373549
 tags = openUILinkIn
 [browser_BrowserUITelemetry_defaults.js]
 skip-if = !e10s # Bug 1373549
 [browser_BrowserUITelemetry_sidebar.js]
 skip-if = !e10s # Bug 1373549
 [browser_BrowserUITelemetry_syncedtabs.js]
 skip-if = !e10s # Bug 1373549
+[browser_BrowserWindowTracker.js]
 [browser_ContentSearch.js]
 support-files =
   contentSearch.js
   contentSearchBadImage.xml
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
   !/browser/components/search/test/head.js
   !/browser/components/search/test/testEngine.xml
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser/browser_BrowserWindowTracker.js
@@ -0,0 +1,113 @@
+"use strict";
+
+ChromeUtils.import("resource:///modules/BrowserWindowTracker.jsm");
+ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+const TEST_WINDOW = window;
+
+async function withOpenWindows(amount, cont) {
+  let windows = [];
+  for (let i = 0; i < amount; ++i) {
+    windows.push(await BrowserTestUtils.openNewBrowserWindow());
+  }
+  await cont(windows);
+  await Promise.all(windows.map(window => BrowserTestUtils.closeWindow(window)));
+}
+
+add_task(async function test_getTopWindow() {
+  await withOpenWindows(5, async function(windows) {
+    // Without options passed in.
+    let window = BrowserWindowTracker.getTopWindow();
+    let expectedMostRecentIndex = windows.length - 1;
+    Assert.equal(window, windows[expectedMostRecentIndex],
+      "Last opened window should be the most recent one.");
+
+    // Mess with the focused window things a bit.
+    for (let idx of [3, 1]) {
+      let promise = BrowserTestUtils.waitForEvent(windows[idx], "activate");
+      Services.focus.focusedWindow = windows[idx];
+      await promise;
+      window = BrowserWindowTracker.getTopWindow();
+      Assert.equal(window, windows[idx], "Lastly focused window should be the most recent one.");
+      // For this test it's useful to keep the array of created windows in order.
+      windows.splice(idx, 1);
+      windows.push(window);
+    }
+    // Update the pointer to the most recent opened window.
+    expectedMostRecentIndex = windows.length - 1;
+
+    // With 'private' option.
+    window = BrowserWindowTracker.getTopWindow({ private: true });
+    Assert.equal(window, null, "No private windows opened yet.");
+    window = BrowserWindowTracker.getTopWindow({ private: 1 });
+    Assert.equal(window, null, "No private windows opened yet.");
+    windows.push(await BrowserTestUtils.openNewBrowserWindow({ private: true }));
+    ++expectedMostRecentIndex;
+    window = BrowserWindowTracker.getTopWindow({ private: true });
+    Assert.equal(window, windows[expectedMostRecentIndex], "Private window available.");
+    window = BrowserWindowTracker.getTopWindow({ private: 1 });
+    Assert.equal(window, windows[expectedMostRecentIndex], "Private window available.");
+    // Private window checks seems to mysteriously fail on Linux in this test.
+    if (AppConstants.platform != "linux") {
+      window = BrowserWindowTracker.getTopWindow({ private: false });
+      Assert.equal(window, windows[expectedMostRecentIndex - 1],
+        "Private window available, but should not be returned.");
+    }
+
+    // With 'allowPopups' option.
+    window = BrowserWindowTracker.getTopWindow({ allowPopups: true });
+    Assert.equal(window, windows[expectedMostRecentIndex],
+      "Window focused before the private window should be the most recent one.");
+    window = BrowserWindowTracker.getTopWindow({ allowPopups: false });
+    Assert.equal(window, windows[expectedMostRecentIndex],
+      "Window focused before the private window should be the most recent one.");
+    let popupWindowPromise = BrowserTestUtils.waitForNewWindow();
+    ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+      let features = "location=no, personalbar=no, toolbar=no, scrollbars=no, menubar=no, status=no";
+      content.window.open("about:blank", "_blank", features);
+    });
+    let popupWindow = await popupWindowPromise;
+    window = BrowserWindowTracker.getTopWindow({ allowPopups: true });
+    Assert.equal(window, popupWindow,
+      "The popup window should be the most recent one, when requested.");
+    window = BrowserWindowTracker.getTopWindow({ allowPopups: false });
+    Assert.equal(window, windows[expectedMostRecentIndex],
+      "Window focused before the popup window should be the most recent one.");
+    popupWindow.close();
+  });
+});
+
+add_task(async function test_orderedWindows() {
+  await withOpenWindows(10, async function(windows) {
+    let ordered = [...BrowserWindowTracker.orderedWindows].filter(w => w != TEST_WINDOW);
+    Assert.deepEqual([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], ordered.map(w => windows.indexOf(w)),
+      "Order of opened windows should be as opened.");
+
+    // Mess with the focused window things a bit.
+    for (let idx of [4, 6, 1]) {
+      let promise = BrowserTestUtils.waitForEvent(windows[idx], "activate");
+      Services.focus.focusedWindow = windows[idx];
+      await promise;
+    }
+
+    let ordered2 = [...BrowserWindowTracker.orderedWindows].filter(w => w != TEST_WINDOW);
+    // After the shuffle, we expect window '1' to be the top-most window, because
+    // it was the last one we called focus on. Then '6', the window we focused
+    // before-last, followed by '4'. The order of the other windows remains
+    // unchanged.
+    let expected = [1, 6, 4, 9, 8, 7, 5, 3, 2, 0];
+    Assert.deepEqual(expected, ordered2.map(w => windows.indexOf(w)),
+      "After shuffle of focused windows, the order should've changed.");
+
+    // Minimizing a window should put it at the end of the ordered list of windows.
+    let promise = BrowserTestUtils.waitForEvent(windows[9], "sizemodechange");
+    windows[9].minimize();
+    await promise;
+
+    let ordered3 = [...BrowserWindowTracker.orderedWindows].filter(w => w != TEST_WINDOW);
+    // Test the end of the array of window indices, because Windows Debug builds
+    // mysteriously swap the order of the first two windows.
+    Assert.deepEqual([8, 7, 5, 3, 2, 0, 9], ordered3.map(w => windows.indexOf(w)).slice(3),
+      "When a window is minimized, the order should've changed.");
+  });
+});
--- a/browser/modules/test/browser/browser_ContentSearch.js
+++ b/browser/modules/test/browser/browser_ContentSearch.js
@@ -367,16 +367,17 @@ var currentStateObj = async function() {
     currentEngine: await currentEngineObj(),
   };
   for (let engine of Services.search.getVisibleEngines()) {
     let uri = engine.getIconURLBySize(16, 16);
     state.engines.push({
       name: engine.name,
       iconBuffer: await arrayBufferFromDataURI(uri),
       hidden: false,
+      identifier: engine.identifier,
     });
   }
   return state;
 };
 
 var currentEngineObj = async function() {
   let engine = Services.search.currentEngine;
   let uriFavicon = engine.getIconURLBySize(16, 16);
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -330,16 +330,17 @@ html|input.urlbar-input {
 }
 
 .urlbar-display {
   margin-top: 0;
   margin-bottom: 0;
   color: GrayText;
 }
 
+#pageAction-urlbar-shareURL,
 #pageAction-panel-shareURL {
   list-style-image: url("chrome://browser/skin/share.svg");
 }
 
 %include ../shared/urlbarSearchSuggestionsNotification.inc.css
 
 /* ----- AUTOCOMPLETE ----- */
 
--- a/browser/themes/osx/share.svg
+++ b/browser/themes/osx/share.svg
@@ -1,7 +1,7 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
-  <path fill="context-fill" d="M12.707 4.294l-4-4A1 1 0 0 0 8.38.077a.984.984 0 0 0-.246-.05A.938.938 0 0 0 8 0a.938.938 0 0 0-.134.027.984.984 0 0 0-.246.05A1 1 0 0 0 7.291.3l-4 4a1 1 0 0 0 1.416 1.408L7 3.415V11a1 1 0 0 0 2 0V3.415l2.293 2.293a1 1 0 0 0 1.414-1.414z"></path>
-  <path fill="context-fill" d="M14 9a1 1 0 0 0-1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-3a1 1 0 0 0-2 0v3a3 3 0 0 0 3 3h8a3 3 0 0 0 3-3v-3a1 1 0 0 0-1-1z"></path>
+  <path fill="context-fill" fill-opacity="context-fill-opacity" d="M12.707 4.294l-4-4A1 1 0 0 0 8.38.077a.984.984 0 0 0-.246-.05A.938.938 0 0 0 8 0a.938.938 0 0 0-.134.027.984.984 0 0 0-.246.05A1 1 0 0 0 7.291.3l-4 4a1 1 0 0 0 1.416 1.408L7 3.415V11a1 1 0 0 0 2 0V3.415l2.293 2.293a1 1 0 0 0 1.414-1.414z"></path>
+  <path fill="context-fill" fill-opacity="context-fill-opacity" d="M14 9a1 1 0 0 0-1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-3a1 1 0 0 0-2 0v3a3 3 0 0 0 3 3h8a3 3 0 0 0 3-3v-3a1 1 0 0 0-1-1z"></path>
 </svg>
--- a/build/upload.py
+++ b/build/upload.py
@@ -1,57 +1,29 @@
 #!/usr/bin/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/.
 #
 # When run directly, this script expects the following environment variables
 # to be set:
-# UPLOAD_HOST    : host to upload files to
-# UPLOAD_USER    : username on that host
-#  and one of the following:
 # UPLOAD_PATH    : path on that host to put the files in
-# UPLOAD_TO_TEMP : upload files to a new temporary directory
-#
-# If UPLOAD_HOST and UPLOAD_USER are not set, this script will simply write out
-# the properties file.
 #
-# If UPLOAD_HOST is "localhost", then files are simply copied to UPLOAD_PATH.
-# In this case, UPLOAD_TO_TEMP and POST_UPLOAD_CMD are not supported, and no
-# properties are written out.
-#
-# And will use the following optional environment variables if set:
-# UPLOAD_SSH_KEY : path to a ssh private key to use
-# UPLOAD_PORT    : port to use for ssh
-# POST_UPLOAD_CMD: a commandline to run on the remote host after uploading.
-#                  UPLOAD_PATH and the full paths of all files uploaded will
-#                  be appended to the commandline.
+# Files are simply copied to UPLOAD_PATH.
 #
 # All files to be uploaded should be passed as commandline arguments to this
 # script. The script takes one other parameter, --base-path, which you can use
 # to indicate that files should be uploaded including their paths relative
 # to the base path.
 
 import sys
 import os
-import re
-import json
-import errno
-import hashlib
 import shutil
 from optparse import OptionParser
-from subprocess import (
-    check_call,
-    check_output,
-    STDOUT,
-    CalledProcessError,
-)
-import concurrent.futures as futures
-import redo
 
 
 def OptionalEnvironmentVariable(v):
     """Return the value of the environment variable named v, or None
     if it's unset (or empty)."""
     if v in os.environ and os.environ[v] != "":
         return os.environ[v]
     return None
@@ -68,245 +40,31 @@ def FixupMsysPath(path):
         if 'SHELL' in os.environ:
             sh = os.environ['SHELL']
             msys = sh[:sh.find('/bin')]
             if path.startswith(msys):
                 path = path[len(msys):]
     return path
 
 
-def WindowsPathToMsysPath(path):
-    """Translate a Windows pathname to an MSYS pathname.
-    Necessary because we call out to ssh/scp, which are MSYS binaries
-    and expect MSYS paths."""
-    # If we're not on Windows, or if we already have an MSYS path (starting
-    # with '/' instead of 'c:' or something), then just return.
-    if sys.platform != 'win32' or path.startswith('/'):
-        return path
-    (drive, path) = os.path.splitdrive(os.path.abspath(path))
-    return "/" + drive[0] + path.replace('\\', '/')
-
-
-def AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key):
-    """Given optional port and ssh key values, append valid OpenSSH
-    commandline arguments to the list cmdline if the values are not None."""
-    if port is not None:
-        cmdline.append("-P%d" % port)
-    if ssh_key is not None:
-        # Don't interpret ~ paths - ssh can handle that on its own
-        if not ssh_key.startswith('~'):
-            ssh_key = WindowsPathToMsysPath(ssh_key)
-        cmdline.extend(["-o", "IdentityFile=%s" % ssh_key])
-    # In case of an issue here we don't want to hang on a password prompt.
-    cmdline.extend(["-o", "BatchMode=yes"])
-
-
-def DoSSHCommand(command, user, host, port=None, ssh_key=None):
-    """Execute command on user@host using ssh. Optionally use
-    port and ssh_key, if provided."""
-    cmdline = ["ssh"]
-    AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key)
-    cmdline.extend(["%s@%s" % (user, host), command])
-
-    with redo.retrying(check_output, sleeptime=10) as f:
-        try:
-            output = f(cmdline, stderr=STDOUT).strip()
-        except CalledProcessError as e:
-            print("failed ssh command output:")
-            print('=' * 20)
-            print(e.output)
-            print('=' * 20)
-            raise
-        return output
-
-    raise Exception("Command %s returned non-zero exit code" % cmdline)
-
-
-def DoSCPFile(file, remote_path, user, host, port=None, ssh_key=None,
-              log=False):
-    """Upload file to user@host:remote_path using scp. Optionally use
-    port and ssh_key, if provided."""
-    if log:
-        print('Uploading %s' % file)
-    cmdline = ["scp"]
-    AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key)
-    cmdline.extend([WindowsPathToMsysPath(file),
-                    "%s@%s:%s" % (user, host, remote_path)])
-    with redo.retrying(check_call, sleeptime=10) as f:
-        f(cmdline)
-        return
-
-    raise Exception("Command %s returned non-zero exit code" % cmdline)
-
-
 def GetBaseRelativePath(path, local_file, base_path):
     """Given a remote path to upload to, a full path to a local file, and an
     optional full path that is a base path of the local file, construct the
     full remote path to place the file in. If base_path is not None, include
     the relative path from base_path to file."""
     if base_path is None or not local_file.startswith(base_path):
-        # Hack to work around OSX uploading the i386 SDK from i386/dist. Both
-        # the i386 SDK and x86-64 SDK end up in the same directory this way.
-        if base_path.endswith('/x86_64/dist'):
-            return GetBaseRelativePath(path, local_file, base_path.replace('/x86_64/', '/i386/'))
         return path
+
     dir = os.path.dirname(local_file)
     # strip base_path + extra slash and make it unixy
     dir = dir[len(base_path) + 1:].replace('\\', '/')
     return path + dir
 
 
-def GetFileHashAndSize(filename):
-    sha512Hash = 'UNKNOWN'
-    size = 'UNKNOWN'
-
-    try:
-        # open in binary mode to make sure we get consistent results
-        # across all platforms
-        with open(filename, "rb") as f:
-            shaObj = hashlib.sha512(f.read())
-            sha512Hash = shaObj.hexdigest()
-
-        size = os.path.getsize(filename)
-    except Exception:
-        raise Exception("Unable to get filesize/hash from file: %s" % filename)
-
-    return (sha512Hash, size)
-
-
-def GetMarProperties(filename):
-    if not os.path.exists(filename):
-        return {}
-    (mar_hash, mar_size) = GetFileHashAndSize(filename)
-    return {
-        'completeMarFilename': os.path.basename(filename),
-        'completeMarSize': mar_size,
-        'completeMarHash': mar_hash,
-    }
-
-
-def GetUrlProperties(output, package):
-    # let's create a switch case using name-spaces/dict
-    # rather than a long if/else with duplicate code
-    property_conditions = [
-        # key: property name, value: condition
-        ('symbolsUrl', lambda m: m.endswith('crashreporter-symbols.zip') or
-         m.endswith('crashreporter-symbols-full.zip')),
-        ('testsUrl', lambda m: m.endswith(('tests.tar.bz2', 'tests.zip'))),
-        ('robocopApkUrl', lambda m: m.endswith('apk') and 'robocop' in m),
-        ('jsshellUrl', lambda m: 'jsshell-' in m and m.endswith('.zip')),
-        ('completeMarUrl', lambda m: m.endswith('.complete.mar')),
-        ('partialMarUrl', lambda m: m.endswith('.mar') and '.partial.' in m),
-        ('codeCoverageURL', lambda m: m.endswith('code-coverage-gcno.zip')),
-        ('sdkUrl', lambda m: m.endswith(('sdk.tar.bz2', 'sdk.zip'))),
-        ('testPackagesUrl', lambda m: m.endswith('test_packages.json')),
-        ('packageUrl', lambda m: m.endswith(package)),
-    ]
-    url_re = re.compile(
-        r'''^(https?://.*?\.(?:tar\.bz2|dmg|zip|apk|rpm|mar|tar\.gz|json))$''')
-    properties = {}
-
-    try:
-        for line in output.splitlines():
-            m = url_re.match(line.strip())
-            if m:
-                m = m.group(1)
-                for prop, condition in property_conditions:
-                    if condition(m):
-                        properties.update({prop: m})
-                        break
-    except IOError as e:
-        if e.errno != errno.ENOENT:
-            raise
-        properties = {prop: 'UNKNOWN' for prop, condition
-                      in property_conditions}
-    return properties
-
-
-def UploadFiles(user, host, path, files, verbose=False, port=None, ssh_key=None, base_path=None,
-                upload_to_temp_dir=False, post_upload_command=None, package=None):
-    """Upload each file in the list files to user@host:path. Optionally pass
-    port and ssh_key to the ssh commands. If base_path is not None, upload
-    files including their path relative to base_path. If upload_to_temp_dir is
-    True files will be uploaded to a temporary directory on the remote server.
-    Generally, you should have a post upload command specified in these cases
-    that can move them around to their correct location(s).
-    If post_upload_command is not None, execute that command on the remote host
-    after uploading all files, passing it the upload path, and the full paths to
-    all files uploaded.
-    If verbose is True, print status updates while working."""
-    if not host or not user:
-        return {}
-    if (not path and not upload_to_temp_dir) or (path and upload_to_temp_dir):
-        print("One (and only one of UPLOAD_PATH or UPLOAD_TO_TEMP must be defined.")
-        sys.exit(1)
-
-    if upload_to_temp_dir:
-        path = DoSSHCommand("mktemp -d", user, host,
-                            port=port, ssh_key=ssh_key)
-    if not path.endswith("/"):
-        path += "/"
-    if base_path is not None:
-        base_path = os.path.abspath(base_path)
-    remote_files = []
-    properties = {}
-
-    def get_remote_path(p):
-        return GetBaseRelativePath(path, os.path.abspath(p), base_path)
-
-    try:
-        # Do a pass to find remote directories so we don't perform excessive
-        # scp calls.
-        remote_paths = set()
-        for file in files:
-            if not os.path.isfile(file):
-                raise IOError("File not found: %s" % file)
-
-            remote_paths.add(get_remote_path(file))
-
-        # If we wanted to, we could reduce the remote paths if they are a parent
-        # of any entry.
-        for p in sorted(remote_paths):
-            DoSSHCommand("mkdir -p " + p, user, host,
-                         port=port, ssh_key=ssh_key)
-
-        with futures.ThreadPoolExecutor(4) as e:
-            fs = []
-            # Since we're uploading in parallel, the largest file should take
-            # the longest to upload. So start it first.
-            for file in sorted(files, key=os.path.getsize, reverse=True):
-                remote_path = get_remote_path(file)
-                fs.append(e.submit(DoSCPFile, file, remote_path, user, host,
-                                   port=port, ssh_key=ssh_key, log=verbose))
-                remote_files.append(remote_path + '/' + os.path.basename(file))
-
-            # We need to call result() on the future otherwise exceptions could
-            # get swallowed.
-            for f in futures.as_completed(fs):
-                f.result()
-
-        if post_upload_command is not None:
-            if verbose:
-                print("Running post-upload command: " + post_upload_command)
-            file_list = '"' + '" "'.join(remote_files) + '"'
-            output = DoSSHCommand('%s "%s" %s' % (
-                post_upload_command, path, file_list), user, host, port=port, ssh_key=ssh_key)
-            # We print since mozharness may parse URLs from the output stream.
-            print(output)
-            properties = GetUrlProperties(output, package)
-    finally:
-        if upload_to_temp_dir:
-            DoSSHCommand("rm -rf %s" % path, user, host, port=port,
-                         ssh_key=ssh_key)
-    if verbose:
-        print("Upload complete")
-    return properties
-
-
-def CopyFilesLocally(path, files, verbose=False, base_path=None, package=None):
+def CopyFilesLocally(path, files, verbose=False, base_path=None):
     """Copy each file in the list of files to `path`.  The `base_path` argument is treated
     as it is by UploadFiles."""
     if not path.endswith("/"):
         path += "/"
     if base_path is not None:
         base_path = os.path.abspath(base_path)
     for file in files:
         file = os.path.abspath(file)
@@ -316,82 +74,31 @@ def CopyFilesLocally(path, files, verbos
         target_path = GetBaseRelativePath(path, file, base_path)
         if not os.path.exists(target_path):
             os.makedirs(target_path)
         if verbose:
             print("Copying " + file + " to " + target_path)
         shutil.copy(file, target_path)
 
 
-def WriteProperties(files, properties_file, url_properties, package):
-    properties = url_properties
-    for file in files:
-        if file.endswith('.complete.mar'):
-            properties.update(GetMarProperties(file))
-    with open(properties_file, 'w') as outfile:
-        properties['packageFilename'] = package
-        properties['uploadFiles'] = [os.path.abspath(f) for f in files]
-        json.dump(properties, outfile, indent=4)
-
-
 if __name__ == '__main__':
-    host = OptionalEnvironmentVariable('UPLOAD_HOST')
-    user = OptionalEnvironmentVariable('UPLOAD_USER')
     path = OptionalEnvironmentVariable('UPLOAD_PATH')
-    upload_to_temp_dir = OptionalEnvironmentVariable('UPLOAD_TO_TEMP')
-    port = OptionalEnvironmentVariable('UPLOAD_PORT')
-    if port is not None:
-        port = int(port)
-    key = OptionalEnvironmentVariable('UPLOAD_SSH_KEY')
-    post_upload_command = OptionalEnvironmentVariable('POST_UPLOAD_CMD')
 
     if sys.platform == 'win32':
         if path is not None:
             path = FixupMsysPath(path)
-        if post_upload_command is not None:
-            post_upload_command = FixupMsysPath(post_upload_command)
 
     parser = OptionParser(usage="usage: %prog [options] <files>")
     parser.add_option("-b", "--base-path",
                       action="store",
                       help="Preserve file paths relative to this path when uploading. "
                       "If unset, all files will be uploaded directly to UPLOAD_PATH.")
-    parser.add_option("--properties-file",
-                      action="store",
-                      help="Path to the properties file to store the upload properties.")
-    parser.add_option("--package",
-                      action="store",
-                      help="Name of the main package.")
     (options, args) = parser.parse_args()
     if len(args) < 1:
         print("You must specify at least one file to upload")
         sys.exit(1)
-    if not options.properties_file:
-        print("You must specify a --properties-file")
-        sys.exit(1)
-
-    if host == "localhost":
-        if upload_to_temp_dir:
-            print("Cannot use UPLOAD_TO_TEMP with UPLOAD_HOST=localhost")
-            sys.exit(1)
-        if post_upload_command:
-            # POST_UPLOAD_COMMAND is difficult to extract from the mozharness
-            # scripts, so just ignore it until it's no longer used anywhere
-            print("Ignoring POST_UPLOAD_COMMAND with UPLOAD_HOST=localhost")
 
     try:
-        if host == "localhost":
-            CopyFilesLocally(path, args, base_path=options.base_path,
-                             package=options.package,
-                             verbose=True)
-        else:
-
-            url_properties = UploadFiles(user, host, path, args,
-                                         base_path=options.base_path, port=port, ssh_key=key,
-                                         upload_to_temp_dir=upload_to_temp_dir,
-                                         post_upload_command=post_upload_command,
-                                         package=options.package, verbose=True)
-
-            WriteProperties(args, options.properties_file,
-                            url_properties, options.package)
+        CopyFilesLocally(path, args, base_path=options.base_path,
+                         verbose=True)
     except IOError as strerror:
         print(strerror)
         sys.exit(1)
--- a/config/recurse.mk
+++ b/config/recurse.mk
@@ -166,16 +166,19 @@ js/xpconnect/src/export: dom/bindings/ex
 accessible/xpcom/export: xpcom/xpidl/export
 
 # The widget binding generator code is part of the annotationProcessors.
 widget/android/bindings/export: build/annotationProcessors/export
 
 # .xpt generation needs the xpidl lex/yacc files
 xpcom/xpidl/export: xpcom/idl-parser/xpidl/export
 
+# CSS2Properties.webidl needs ServoCSSPropList.py from layout/style
+dom/bindings/export: layout/style/export
+
 ifdef ENABLE_CLANG_PLUGIN
 $(filter-out config/host build/unix/stdc++compat/% build/clang-plugin/%,$(compile_targets)): build/clang-plugin/target build/clang-plugin/tests/target
 build/clang-plugin/tests/target: build/clang-plugin/target
 endif
 
 # Interdependencies that moz.build world don't know about yet for compilation.
 # Note some others are hardcoded or "guessed" in recursivemake.py and emitter.py
 ifeq ($(MOZ_WIDGET_TOOLKIT),gtk3)
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -31,17 +31,17 @@ ifdef REBUILD_CHECK
 REPORT_BUILD = $(info $(shell $(PYTHON) $(MOZILLA_DIR)/config/rebuild_check.py $@ $^))
 REPORT_BUILD_VERBOSE = $(REPORT_BUILD)
 else
 REPORT_BUILD = $(info $(notdir $@))
 
 ifdef BUILD_VERBOSE_LOG
 REPORT_BUILD_VERBOSE = $(REPORT_BUILD)
 else
-REPORT_BUILD_VERBOSE =
+REPORT_BUILD_VERBOSE = $(call BUILDSTATUS,BUILD_VERBOSE $(relativesrcdir))
 endif
 
 endif
 
 EXEC			= exec
 
 # ELOG prints out failed command when building silently (gmake -s). Pymake
 # prints out failed commands anyway, so ELOG just makes things worse by
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -408,16 +408,18 @@ TabTarget.prototype = {
 
       // When connecting to a local tab, we only need the root actor.
       // Then we are going to call DebuggerServer.connectToFrame and talk
       // directly with actors living in the child process.
       // We also need browser actors for actor registry which enabled addons
       // to register custom actors.
       // TODO: the comment and implementation are out of sync here. See Bug 1420134.
       DebuggerServer.registerAllActors();
+      // Enable being able to get child process actors
+      DebuggerServer.allowChromeProcess = true;
 
       this._client = new DebuggerClient(DebuggerServer.connectPipe());
       // A local TabTarget will never perform chrome debugging.
       this._chrome = false;
     } else if (this._form.isWebExtension &&
           this.client.mainRoot.traits.webExtensionAddonConnect) {
       // The addonActor form is related to a WebExtensionParentActor instance,
       // which isn't a tab actor on its own, it is an actor living in the parent process
--- a/devtools/docs/contributing/making-prs.md
+++ b/devtools/docs/contributing/making-prs.md
@@ -1,88 +1,48 @@
 # Creating and sending patches <!--TODO: (in the future: Making Pull Requests)-->
 
-<!-- TODO: this will need to be updated in the future when we move to GitHub -->
-
 ## Creating a patch
 
 To create a patch you need to first commit your changes and then export them to a patch file.
 
-With Mercurial:
-* `hg commit -m 'your commit message'`
-* `hg export > /path/to/your/patch`
-
-With Git, the process is similar, but you first need to add an alias to create Mercurial-style patches. Have a look at [the detailed documentation](https://developer.mozilla.org/en-US/docs/Tools/Contributing#Creating_a_patch_to_check_in).
+```
+hg commit -m 'your commit message'
+hg export > /path/to/your/patch
+```
 
 ## Commit messages
 
-Commit messages should follow the pattern `Bug 1234567 - change description. r=reviewer`
+Commit messages should follow the pattern `Bug 1234567 - change description. r=reviewer`.
 
 First is the bug number related to the patch. Then the description should explain what the patch changes. The last part is used to keep track of the reviewer for the patch.
 
 ## Submitting a patch
 
 Once you have a patch file, add it as an attachment to the Bugzilla ticket you are working on and add the `feedback?` or `review?` flag depending on if you just want feedback and confirmation you're doing the right thing or if you think the patch is ready to land respectively. Read more about [how to submit a patch and the Bugzilla review cycle here](https://developer.mozilla.org/en-US/docs/Developer_Guide/How_to_Submit_a_Patch).
 
 You can also take a look at the [Code Review Checklist](./code-reviews.md) as it contains a list of checks that your reviewer is likely to go over when reviewing your code.
 
 ## Squashing commits
 
-Sometimes you may be asked to squash your commits. Squashing means merging multiple commits into one in case you created multiple commits while working on a bug. Squashing bugs is easy with steps listed below for both git and mercurial.
+Sometimes you may be asked to squash your commits. Squashing means merging multiple commits into one in case you created multiple commits while working on a bug. Squashing bugs is easy!
 
-### With Mercurial:
+We will use the histedit extension for squashing commits in Mercurial. You can check if this extension is enabled in your Mercurial installation following these steps:
 
-We will use the histedit extension for squashing commits in Mercurial. You can check if this extension is enabled in your Mercurial following these steps:
-* Open `.hgrc` (default configuration file of mercurial) located in the home directory on Linux/OSX, using your favourite editor.
+* Open `.hgrc` (Linux/OSX) or `Mercurial.ini` (Windows) –this is the default configuration file of Mercurial– located in your home directory, using your favourite editor.
 * Then add `histedit= ` under the `[extensions]` list present in file, if not present already.
 
 Then, run the following command:
 
 `hg histedit`
 
 You will see something like this on your terminal:
 
 ```
 pick 3bd22d1cc59a 0 "First-Commit-Message"
 pick 81c4d40e57d3 1 "Second-Commit-Message"
 ```
 
-These lines represent your commits. Suppose we want to merge 81c4d40e57d3 to 3bd22d1cc59a. Then replace **pick** in front of 81c4d40e57d3 with **fold** (or simply 'f'). Save the changes.
-
-You will see that 81c4d40e57d3 has been combined with 3bd22d1cc59a. You can verify this using `hg log` command.
-
-You can use fold to as many commits you want and they will be combined with the first commit above them which does not use fold.
-
-### With Git:
-
-To squash commits in git we will use the rebase command. Let's learn the syntax using an example:
-
-`git rebase -i HEAD~2`
-
-Above commands is for rebasing commits.
-* The -i flag is to run rebase in interactive mode.
-* HEAD~2 is to rebase the last two commits.
-
-You can use any number, depending on how many commits you want to rebase. 
-
-After you run that command, an editor will open displaying contents similar to this:
+These lines represent your commits. Suppose we want to merge `81c4d40e57d3` to `3bd22d1cc59a`. Then replace **pick** in front of `81c4d40e57d3` with **fold** (or simply 'f'). Save the changes.
 
-```
-pick 5878025 "First-Commit-Message"
-pick bd1efe7 "Second-Commit-Message"
-```
-
-Suppose you want to merge `bd1efe7` to `5878025`, then replace **pick** with **squash** (or simply 's') and save the changes and close the editor, then a file should open like this:
-
-```
-    # This is a combination of 2 commits.
-    # The first commit's message is:
+You will see that `81c4d40e57d3` has been combined with `3bd22d1cc59a`. You can verify this using the `hg log` command.
 
-    < First-Commit-Message >
-
-    This is the 2nd commit message:
-
-    < Second-Commit-Message >
-
-```
-Write a new commit message (note that this will be a new commit message used for squashed commits). Every line not starting with # above contributes to the commit message.
-
-Save your changes and you are done. You will see that your commits have been squashed and you can verify this using `git log` or `git reflog`.
+You can fold as many commits you want, and they will be combined with the first commit above them which does not use fold.
--- a/devtools/docs/getting-started/build.md
+++ b/devtools/docs/getting-started/build.md
@@ -1,41 +1,39 @@
 # Set up to build Firefox Developer Tools
 
 These are the steps we're going to look at:
 
 * [Getting the code](#getting-the-code)
-  * [using Mercurial](#using-mercurial-hg)
-  * [using Git](#using-git)
 * [Building and running locally](#building-and-running-locally)
   * [Rebuilding](#rebuilding)
   * [Artifact builds](#building-even-faster-with-artifact-builds) for even faster builds
   * [Maybe you don't even need to build](#maybe-you-dont-even-need-to-build)
 
 ## Getting the code
 
-The code is officially hosted on [a Mercurial repository](https://hg.mozilla.org/mozilla-central/). Despair not! There are ways of accessing this via git. We will explain this too.
-
-Either way takes a long time, because the repository is **B I G**. So be prepared to be patient.
-
-### Using Mercurial (hg)
+The code is officially hosted on [a Mercurial repository](https://hg.mozilla.org/mozilla-central/). It takes a long time to clone, because the repository is **B I G**. So be prepared to be patient.
 
 ```bash
 hg clone http://hg.mozilla.org/mozilla-central
 ```
 
-### Using git
-
-There is a tool called [git-cinnabar](https://github.com/glandium/git-cinnabar/) that lets you use git on top of a Mercurial repository. There's a bit of setup involved, so we've written [a script to automate](https://github.com/sole/cinnabarify) installing `git-cinnabar` and obtaining the code.
-
 ## Building and running locally
 
-Whatever method you used to obtain the code, the build step is the same. Fortunately, the Firefox team has made a very good job of automating this with bootstrap scripts and putting [documentation](https://developer.mozilla.org/En/Simple_Firefox_build) together.
+Fortunately, the Firefox team has made a very good job of automating the building process with bootstrap scripts and putting [documentation](https://developer.mozilla.org/En/Simple_Firefox_build) together.
+
+The very first time you are building Firefox, run:
 
-Run:
+```bash
+./mach bootstrap
+./mach configure
+./mach build
+```
+
+After that first successful build, you can just run:
 
 ```bash
 ./mach build
 ```
 
 If your system needs additional dependencies installed (for example, Python, or a compiler, etc), various diagnostic messages will be printed to your screen. Follow their advice and try building again.
 
 Building also takes a long time (specially on slow computers).
@@ -106,12 +104,11 @@ With pseudocode:
 ./mach run
 # 3. you try out things in the browser that opens
 # 4. fully close the browser, e.g. ⌘Q in MacOS
 # 5. edit JS files on the `devtools` folder, save
 # 6. Back to step 2!
 ./mach run
 ```
 
-While not as fast as the average "save file and reload the website" *web development workflow*, or newer workflows such as React's reloading, this can still be quite fast. 
+While not as fast as the average "save file and reload the website" *web development workflow*, or newer workflows such as React's reloading, this can still be quite fast.
 
 And certainly faster than building each time, even with artifact builds.
-
--- a/devtools/shared/css/generated/mach_commands.py
+++ b/devtools/shared/css/generated/mach_commands.py
@@ -45,42 +45,27 @@ class MachCommands(MachCommandBase):
 
         self.output_template({
             'preferences': stringify(preferences),
             'cssProperties': stringify(db['cssProperties']),
             'pseudoElements': stringify(db['pseudoElements'])})
 
     def get_preferences(self):
         """Get all of the preferences associated with enabling and disabling a property."""
-        # Build the command to run the preprocessor on PythonCSSProps.h
-        headerPath = resolve_path(self.topsrcdir, 'layout/style/PythonCSSProps.h')
-
-        cpp = self.substs['CPP']
-
-        if not cpp:
-            print("Unable to find the cpp program. Please do a full, nonartifact")
-            print("build and try this again.")
-            sys.exit(1)
-
-        if type(cpp) is list:
-            cmd = cpp
-        else:
-            cmd = shellutil.split(cpp)
-        cmd += shellutil.split(self.substs['ACDEFINES'])
-        cmd.append(headerPath)
-
-        # The preprocessed list takes the following form:
+        # The data takes the following form:
         # [ (name, prop, id, flags, pref, proptype), ... ]
-        preprocessed = eval(subprocess.check_output(cmd))
+        dataPath = resolve_path(self.topobjdir, 'layout/style/ServoCSSPropList.py')
+        with open(dataPath, "r") as f:
+            data = eval(f.read())
 
         # Map this list
         # (name, prop, id, flags, pref, proptype) => (name, pref)
         preferences = [
             (name, pref)
-            for name, prop, id, flags, pref, proptype in preprocessed
+            for name, prop, id, flags, pref, proptype in data
             if 'CSS_PROPERTY_INTERNAL' not in flags and pref]
 
         return preferences
 
     def get_properties_db_from_xpcshell(self):
         """Generate the static css properties db for devtools from an xpcshell script."""
         build = MozbuildObject.from_environment()
 
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -2991,21 +2991,21 @@ exports.CSS_PROPERTIES = {
       "grid-row-end",
       "grid-row-gap",
       "grid-row-start",
       "grid-template-areas",
       "grid-template-columns",
       "grid-template-rows",
       "height",
       "hyphens",
-      "initial-letter",
       "image-orientation",
       "-moz-image-region",
       "image-rendering",
       "ime-mode",
+      "initial-letter",
       "inline-size",
       "isolation",
       "justify-content",
       "justify-items",
       "justify-self",
       "left",
       "letter-spacing",
       "lighting-color",
@@ -3059,18 +3059,21 @@ exports.CSS_PROPERTIES = {
       "-moz-outline-radius-bottomleft",
       "-moz-outline-radius-bottomright",
       "-moz-outline-radius-topleft",
       "-moz-outline-radius-topright",
       "outline-style",
       "outline-width",
       "overflow-clip-box-block",
       "overflow-clip-box-inline",
+      "overflow-wrap",
       "overflow-x",
       "overflow-y",
+      "overscroll-behavior-x",
+      "overscroll-behavior-y",
       "padding-block-end",
       "padding-block-start",
       "padding-bottom",
       "padding-inline-end",
       "padding-inline-start",
       "padding-left",
       "padding-right",
       "padding-top",
@@ -3083,19 +3086,18 @@ exports.CSS_PROPERTIES = {
       "pointer-events",
       "position",
       "quotes",
       "resize",
       "right",
       "rotate",
       "ruby-align",
       "ruby-position",
+      "scale",
       "scroll-behavior",
-      "overscroll-behavior-x",
-      "overscroll-behavior-y",
       "scroll-snap-coordinate",
       "scroll-snap-destination",
       "scroll-snap-points-x",
       "scroll-snap-points-y",
       "scroll-snap-type-x",
       "scroll-snap-type-y",
       "shape-image-threshold",
       "shape-outside",
@@ -3128,17 +3130,16 @@ exports.CSS_PROPERTIES = {
       "text-justify",
       "text-orientation",
       "text-overflow",
       "text-rendering",
       "text-shadow",
       "-moz-text-size-adjust",
       "-webkit-text-stroke-color",
       "-webkit-text-stroke-width",
-      "scale",
       "text-transform",
       "top",
       "-moz-top-layer",
       "touch-action",
       "transform",
       "transform-box",
       "transform-origin",
       "transform-style",
@@ -3153,23 +3154,22 @@ exports.CSS_PROPERTIES = {
       "-moz-user-select",
       "vector-effect",
       "vertical-align",
       "visibility",
       "white-space",
       "width",
       "will-change",
       "-moz-window-dragging",
+      "-moz-window-opacity",
       "-moz-window-shadow",
-      "-moz-window-opacity",
       "-moz-window-transform",
       "-moz-window-transform-origin",
       "word-break",
       "word-spacing",
-      "overflow-wrap",
       "writing-mode",
       "z-index"
     ],
     "supports": [
       1,
       2,
       4,
       5,
@@ -9475,20 +9475,16 @@ exports.PSEUDO_ELEMENTS = [
 ];
 
 /**
  * A list of the preferences keys for whether a CSS property is enabled or not. This is
  * exposed for testing purposes.
  */
 exports.PREFERENCES = [
   [
-    "all",
-    "layout.css.all-shorthand.enabled"
-  ],
-  [
     "background-blend-mode",
     "layout.css.background-blend-mode.enabled"
   ],
   [
     "box-decoration-break",
     "layout.css.box-decoration-break.enabled"
   ],
   [
@@ -9507,88 +9503,80 @@ exports.PREFERENCES = [
     "font-optical-sizing",
     "layout.css.font-variations.enabled"
   ],
   [
     "font-variation-settings",
     "layout.css.font-variations.enabled"
   ],
   [
+    "image-orientation",
+    "layout.css.image-orientation.enabled"
+  ],
+  [
     "initial-letter",
     "layout.css.initial-letter.enabled"
   ],
   [
-    "image-orientation",
-    "layout.css.image-orientation.enabled"
-  ],
-  [
     "isolation",
     "layout.css.isolation.enabled"
   ],
   [
     "mix-blend-mode",
     "layout.css.mix-blend-mode.enabled"
   ],
   [
     "-moz-osx-font-smoothing",
     "layout.css.osx-font-smoothing.enabled"
   ],
   [
-    "overflow-clip-box",
-    "layout.css.overflow-clip-box.enabled"
-  ],
-  [
     "overflow-clip-box-block",
     "layout.css.overflow-clip-box.enabled"
   ],
   [
     "overflow-clip-box-inline",
     "layout.css.overflow-clip-box.enabled"
   ],
   [
-    "rotate",
-    "layout.css.individual-transform.enabled"
-  ],
-  [
-    "scroll-behavior",
-    "layout.css.scroll-behavior.property-enabled"
-  ],
-  [
-    "overscroll-behavior",
-    "layout.css.overscroll-behavior.enabled"
-  ],
-  [
     "overscroll-behavior-x",
     "layout.css.overscroll-behavior.enabled"
   ],
   [
     "overscroll-behavior-y",
     "layout.css.overscroll-behavior.enabled"
   ],
   [
+    "rotate",
+    "layout.css.individual-transform.enabled"
+  ],
+  [
+    "scale",
+    "layout.css.individual-transform.enabled"
+  ],
+  [
+    "scroll-behavior",
+    "layout.css.scroll-behavior.property-enabled"
+  ],
+  [
     "scroll-snap-coordinate",
     "layout.css.scroll-snap.enabled"
   ],
   [
     "scroll-snap-destination",
     "layout.css.scroll-snap.enabled"
   ],
   [
     "scroll-snap-points-x",
     "layout.css.scroll-snap.enabled"
   ],
   [
     "scroll-snap-points-y",
     "layout.css.scroll-snap.enabled"
   ],
   [
-    "scroll-snap-type",
-    "layout.css.scroll-snap.enabled"
-  ],
-  [
     "scroll-snap-type-x",
     "layout.css.scroll-snap.enabled"
   ],
   [
     "scroll-snap-type-y",
     "layout.css.scroll-snap.enabled"
   ],
   [
@@ -9607,90 +9595,54 @@ exports.PREFERENCES = [
     "-webkit-text-fill-color",
     "layout.css.prefixes.webkit"
   ],
   [
     "text-justify",
     "layout.css.text-justify.enabled"
   ],
   [
-    "-webkit-text-stroke",
-    "layout.css.prefixes.webkit"
-  ],
-  [
     "-webkit-text-stroke-color",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-text-stroke-width",
     "layout.css.prefixes.webkit"
   ],
   [
-    "scale",
-    "layout.css.individual-transform.enabled"
-  ],
-  [
     "touch-action",
     "layout.css.touch_action.enabled"
   ],
   [
     "transform-box",
     "svg.transform-box.enabled"
   ],
   [
     "translate",
     "layout.css.individual-transform.enabled"
   ],
   [
-    "-moz-transform",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-transform-origin",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-perspective-origin",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-perspective",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-transform-style",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-backface-visibility",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-border-image",
-    "layout.css.prefixes.border-image"
-  ],
-  [
-    "-moz-transition",
-    "layout.css.prefixes.transitions"
-  ],
-  [
-    "-moz-transition-delay",
-    "layout.css.prefixes.transitions"
-  ],
-  [
-    "-moz-transition-duration",
-    "layout.css.prefixes.transitions"
-  ],
-  [
-    "-moz-transition-property",
-    "layout.css.prefixes.transitions"
-  ],
-  [
-    "-moz-transition-timing-function",
-    "layout.css.prefixes.transitions"
+    "all",
+    "layout.css.all-shorthand.enabled"
+  ],
+  [
+    "overflow-clip-box",
+    "layout.css.overflow-clip-box.enabled"
+  ],
+  [
+    "overscroll-behavior",
+    "layout.css.overscroll-behavior.enabled"
+  ],
+  [
+    "scroll-snap-type",
+    "layout.css.scroll-snap.enabled"
+  ],
+  [
+    "-webkit-text-stroke",
+    "layout.css.prefixes.webkit"
   ],
   [
     "-moz-animation",
     "layout.css.prefixes.animations"
   ],
   [
     "-moz-animation-delay",
     "layout.css.prefixes.animations"
@@ -9719,30 +9671,90 @@ exports.PREFERENCES = [
     "-moz-animation-play-state",
     "layout.css.prefixes.animations"
   ],
   [
     "-moz-animation-timing-function",
     "layout.css.prefixes.animations"
   ],
   [
+    "-moz-backface-visibility",
+    "layout.css.prefixes.transforms"
+  ],
+  [
+    "-moz-border-image",
+    "layout.css.prefixes.border-image"
+  ],
+  [
     "-moz-box-sizing",
     "layout.css.prefixes.box-sizing"
   ],
   [
+    "-moz-column-span",
+    "layout.css.column-span.enabled"
+  ],
+  [
     "-moz-font-feature-settings",
     "layout.css.prefixes.font-features"
   ],
   [
     "-moz-font-language-override",
     "layout.css.prefixes.font-features"
   ],
   [
-    "-moz-column-span",
-    "layout.css.column-span.enabled"
+    "-moz-perspective",
+    "layout.css.prefixes.transforms"
+  ],
+  [
+    "-moz-perspective-origin",
+    "layout.css.prefixes.transforms"
+  ],
+  [
+    "-moz-transform",
+    "layout.css.prefixes.transforms"
+  ],
+  [
+    "-moz-transform-origin",
+    "layout.css.prefixes.transforms"
+  ],
+  [
+    "-moz-transform-style",
+    "layout.css.prefixes.transforms"
+  ],
+  [
+    "-moz-transition",
+    "layout.css.prefixes.transitions"
+  ],
+  [
+    "-moz-transition-delay",
+    "layout.css.prefixes.transitions"
+  ],
+  [
+    "-moz-transition-duration",
+    "layout.css.prefixes.transitions"
+  ],
+  [
+    "-moz-transition-property",
+    "layout.css.prefixes.transitions"
+  ],
+  [
+    "-moz-transition-timing-function",
+    "layout.css.prefixes.transitions"
+  ],
+  [
+    "-webkit-align-content",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-align-items",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-align-self",
+    "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-animation",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-animation-delay",
     "layout.css.prefixes.webkit"
@@ -9771,188 +9783,124 @@ exports.PREFERENCES = [
     "-webkit-animation-play-state",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-animation-timing-function",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-filter",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-text-size-adjust",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transform",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transform-origin",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transform-style",
-    "layout.css.prefixes.webkit"
-  ],
-  [
     "-webkit-backface-visibility",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-perspective",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-perspective-origin",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition-delay",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition-duration",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition-property",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition-timing-function",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-top-left-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-top-right-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-bottom-left-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-bottom-right-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
     "-webkit-background-clip",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-background-origin",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-background-size",
     "layout.css.prefixes.webkit"
   ],
   [
+    "-webkit-border-bottom-left-radius",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-border-bottom-right-radius",
+    "layout.css.prefixes.webkit"
+  ],
+  [
     "-webkit-border-image",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-box-shadow",
+    "-webkit-border-radius",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-border-top-left-radius",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-box-sizing",
+    "-webkit-border-top-right-radius",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-box-align",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-box-direction",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-box-flex",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-box-ordinal-group",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-box-orient",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-box-direction",
+    "-webkit-box-pack",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-box-shadow",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-box-sizing",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-box-align",
+    "-webkit-filter",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-box-pack",
+    "-webkit-flex",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-flex-basis",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-flex-direction",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-flex-wrap",
-    "layout.css.prefixes.webkit"
-  ],
-  [
     "-webkit-flex-flow",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-order",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-flex",
-    "layout.css.prefixes.webkit"
-  ],
-  [
     "-webkit-flex-grow",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-flex-shrink",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-flex-basis",
+    "-webkit-flex-wrap",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-justify-content",
     "layout.css.prefixes.webkit"
   ],
   [
-    "-webkit-align-items",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-align-self",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-align-content",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-user-select",
-    "layout.css.prefixes.webkit"
-  ],
-  [
     "-webkit-mask",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-mask-clip",
     "layout.css.prefixes.webkit"
   ],
   [
@@ -9981,10 +9929,62 @@ exports.PREFERENCES = [
   ],
   [
     "-webkit-mask-repeat",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-mask-size",
     "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-order",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-perspective",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-perspective-origin",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-text-size-adjust",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-transform",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-transform-origin",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-transform-style",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-transition",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-transition-delay",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-transition-duration",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-transition-property",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-transition-timing-function",
+    "layout.css.prefixes.webkit"
+  ],
+  [
+    "-webkit-user-select",
+    "layout.css.prefixes.webkit"
   ]
 ];
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -97,62 +97,16 @@
 namespace mozilla {
 namespace dom {
 
 static bool sVibratorEnabled   = false;
 static uint32_t sMaxVibrateMS  = 0;
 static uint32_t sMaxVibrateListLen = 0;
 static const char* kVibrationPermissionType = "vibration";
 
-static void
-AddPermission(nsIPrincipal* aPrincipal, const char* aType, uint32_t aPermission,
-              uint32_t aExpireType, int64_t aExpireTime)
-{
-  MOZ_ASSERT(aType);
-  MOZ_ASSERT(aPrincipal);
-
-  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
-  if (!permMgr) {
-    return;
-  }
-  permMgr->AddFromPrincipal(aPrincipal, aType, aPermission, aExpireType,
-                            aExpireTime);
-}
-
-static uint32_t
-GetPermission(nsPIDOMWindowInner* aWindow, const char* aType)
-{
-  MOZ_ASSERT(aType);
-
-  uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
-
-  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
-  if (!permMgr) {
-    return permission;
-  }
-  permMgr->TestPermissionFromWindow(aWindow, aType, &permission);
-  return permission;
-}
-
-static uint32_t
-GetPermission(nsIPrincipal* aPrincipal, const char* aType)
-{
-  MOZ_ASSERT(aType);
-  MOZ_ASSERT(aPrincipal);
-
-  uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
-
-  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
-  if (!permMgr) {
-    return permission;
-  }
-  permMgr->TestPermissionFromPrincipal(aPrincipal, aType, &permission);
-  return permission;
-}
-
 /* static */
 void
 Navigator::Init()
 {
   Preferences::AddBoolVarCache(&sVibratorEnabled,
                                "dom.vibrator.enabled", true);
   Preferences::AddUintVarCache(&sMaxVibrateMS,
                                "dom.vibrator.max_vibrate_ms", 10000);
@@ -839,20 +793,24 @@ Navigator::SetVibrationPermission(bool a
     } else {
       gVibrateWindowListener->RemoveListener();
     }
     gVibrateWindowListener = new VibrateWindowListener(mWindow, doc);
     hal::Vibrate(pattern, mWindow);
   }
 
   if (aPersistent) {
-    AddPermission(doc->NodePrincipal(), kVibrationPermissionType,
-                  aPermitted ? nsIPermissionManager::ALLOW_ACTION :
-                               nsIPermissionManager::DENY_ACTION,
-                  nsIPermissionManager::EXPIRE_SESSION, 0);
+    nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+    if (!permMgr) {
+      return;
+    }
+    permMgr->AddFromPrincipal(doc->NodePrincipal(), kVibrationPermissionType,
+                              aPermitted ? nsIPermissionManager::ALLOW_ACTION :
+                                           nsIPermissionManager::DENY_ACTION,
+                              nsIPermissionManager::EXPIRE_SESSION, 0);
   }
 }
 
 bool
 Navigator::Vibrate(uint32_t aDuration)
 {
   AutoTArray<uint32_t, 1> pattern;
   pattern.AppendElement(aDuration);
@@ -886,17 +844,25 @@ Navigator::Vibrate(const nsTArray<uint32
 
   // The spec says we check sVibratorEnabled after we've done the sanity
   // checking on the pattern.
   if (!sVibratorEnabled) {
     return true;
   }
 
   mRequestedVibrationPattern.SwapElements(pattern);
-  uint32_t permission = GetPermission(mWindow, kVibrationPermissionType);
+  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+  if (!permMgr) {
+    return false;
+  }
+
+  uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
+
+  permMgr->TestPermissionFromPrincipal(doc->NodePrincipal(), kVibrationPermissionType,
+                                       &permission);
 
   if (permission == nsIPermissionManager::ALLOW_ACTION ||
       mRequestedVibrationPattern.IsEmpty() ||
       (mRequestedVibrationPattern.Length() == 1 &&
        mRequestedVibrationPattern[0] == 0)) {
     // Always allow cancelling vibration and respect session permissions.
     SetVibrationPermission(true /* permitted */, false /* persistent */);
     return true;
@@ -1548,67 +1514,24 @@ Navigator::OnNavigation()
 
   // If MediaManager is open let it inform any live streams or pending callbacks
   MediaManager *manager = MediaManager::GetIfExists();
   if (manager) {
     manager->OnNavigation(mWindow->WindowID());
   }
 }
 
-bool
-Navigator::CheckPermission(const char* type)
-{
-  return CheckPermission(mWindow, type);
-}
-
-/* static */
-bool
-Navigator::CheckPermission(nsPIDOMWindowInner* aWindow, const char* aType)
-{
-  if (!aWindow) {
-    return false;
-  }
-
-  uint32_t permission = GetPermission(aWindow, aType);
-  return permission == nsIPermissionManager::ALLOW_ACTION;
-}
-
 JSObject*
 Navigator::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
 {
   return NavigatorBinding::Wrap(cx, this, aGivenProto);
 }
 
 /* static */
 bool
-Navigator::HasWakeLockSupport(JSContext* /* unused*/, JSObject* /*unused */)
-{
-  nsCOMPtr<nsIPowerManagerService> pmService =
-    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
-  // No service means no wake lock support
-  return !!pmService;
-}
-
-/* static */
-bool
-Navigator::HasWifiManagerSupport(JSContext* /* unused */,
-                                 JSObject* aGlobal)
-{
-  // On XBL scope, the global object is NOT |window|. So we have
-  // to use nsContentUtils::GetObjectPrincipal to get the principal
-  // and test directly with permission manager.
-
-  nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal);
-  uint32_t permission = GetPermission(principal, "wifi-manage");
-
-  return permission == nsIPermissionManager::ALLOW_ACTION;
-}
-
-/* static */
-bool
 Navigator::HasUserMediaSupport(JSContext* /* unused */,
                                JSObject* /* unused */)
 {
   // Make enabling peerconnection enable getUserMedia() as well
   return Preferences::GetBool("media.navigator.enabled", false) ||
          Preferences::GetBool("media.peerconnection.enabled", false);
 }
 
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -208,19 +208,16 @@ public:
 
   void GetLanguages(nsTArray<nsString>& aLanguages);
 
   StorageManager* Storage();
 
   static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
 
   // WebIDL helper methods
-  static bool HasWakeLockSupport(JSContext* /* unused*/, JSObject* /*unused */);
-  static bool HasWifiManagerSupport(JSContext* /* unused */,
-                                  JSObject* aGlobal);
   static bool HasUserMediaSupport(JSContext* /* unused */,
                                   JSObject* /* unused */);
 
   nsPIDOMWindowInner* GetParentObject() const
   {
     return GetWindow();
   }
 
@@ -239,19 +236,16 @@ private:
 
 public:
   void NotifyVRDisplaysUpdated();
   void NotifyActiveVRDisplaysChanged();
 
 private:
   virtual ~Navigator();
 
-  bool CheckPermission(const char* type);
-  static bool CheckPermission(nsPIDOMWindowInner* aWindow, const char* aType);
-
   // This enum helps SendBeaconInternal to apply different behaviors to body
   // types.
   enum BeaconType {
     eBeaconTypeBlob,
     eBeaconTypeArrayBuffer,
     eBeaconTypeOther
   };
 
--- a/dom/base/UseCounters.conf
+++ b/dom/base/UseCounters.conf
@@ -14,21 +14,18 @@
 //
 //   (c) one of four possible use counter declarations:
 //
 //         method <IDL interface name>.<IDL operation name>
 //         attribute <IDL interface name>.<IDL attribute name>
 //         property <CSS property method name>
 //         custom <any valid identifier> <description>
 //
-// The |CSS property method name| should be identical to the |method|
-// argument to CSS_PROP and related macros.  The method name is
-// identical to the name of the property, except that all hyphens are
-// removed and CamelCase naming is used.  See nsCSSPropList.h for
-// further details.
+// The |CSS property method name| should be CamelCase form of the property
+// name with -moz- and -x- prefix removed.
 //
 // The <description> for custom counters will be appended to "When a document "
 // or "When a page ", so phrase it appropriately.  For instance, "constructs a
 // Foo object" or "calls Document.bar('some value')".  It may contain any
 // character (including whitespace).
 //
 // To actually cause use counters to be incremented, DOM methods
 // and attributes must have a [UseCounter] extended attribute in
--- a/dom/base/gen-usecounters.py
+++ b/dom/base/gen-usecounters.py
@@ -56,20 +56,25 @@ def generate_list(f, counters):
     print_optional_macro_undeclare('USE_COUNTER_CSS_PROPERTY')
     print_optional_macro_undeclare('USE_COUNTER_CUSTOM')
 
 def generate_property_map(f, counters):
     print(AUTOGENERATED_WARNING_COMMENT, file=f)
     print('''
 enum {
   #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
-  #define CSS_PROP(name_, id_, method_, ...) \\
+  // Need an extra level of macro nesting to force expansion of method_
+  // params before they get pasted.
+  #define CSS_PROP_USE_COUNTER(method_) \\
     USE_COUNTER_FOR_CSS_PROPERTY_##method_ = eUseCounter_UNKNOWN,
-  #include "nsCSSPropList.h"
-  #undef CSS_PROP
+  #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \\
+    CSS_PROP_USE_COUNTER(method_)
+  #include "mozilla/ServoCSSPropList.h"
+  #undef CSS_PROP_LONGHAND
+  #undef CSS_PROP_USE_COUNTER
   #undef CSS_PROP_PUBLIC_OR_PRIVATE
 };
 ''', file=f)
     for counter in counters:
         if counter['type'] == 'property':
             prop = counter['property_name']
             print('#define USE_COUNTER_FOR_CSS_PROPERTY_%s eUseCounter_property_%s' % (prop, prop), file=f)
 
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -8,16 +8,17 @@
 #include "nsWindowSizes.h"
 #include "nsGlobalWindow.h"
 #include "nsIDocument.h"
 #include "nsIDOMWindowCollection.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
 #include "nsNetCID.h"
 #include "nsPrintfCString.h"
 #include "XPCJSMemoryReporter.h"
 #include "js/MemoryMetrics.h"
 #include "nsQueryObject.h"
 #include "nsServiceManagerUtils.h"
 #ifdef MOZ_XUL
 #include "nsXULPrototypeCache.h"
@@ -895,16 +896,19 @@ nsWindowMemoryReporter::CheckForGhostWin
         // that is not null.
         mGhostWindowCount++;
         if (aOutGhostIDs && window) {
           aOutGhostIDs->PutEntry(window->WindowID());
         }
       }
     }
   }
+
+  Telemetry::ScalarSetMaximum(Telemetry::ScalarID::MEMORYREPORTER_MAX_GHOST_WINDOWS,
+                              mGhostWindowCount);
 }
 
 /* static */ int64_t
 nsWindowMemoryReporter::GhostWindowsDistinguishedAmount()
 {
   return sWindowReporter->mGhostWindowCount;
 }
 
--- a/dom/bindings/GenerateCSS2PropertiesWebIDL.py
+++ b/dom/bindings/GenerateCSS2PropertiesWebIDL.py
@@ -1,34 +1,27 @@
 # 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/.
 
 import sys
 import string
 import argparse
-import subprocess
-import buildconfig
-from mozbuild import shellutil
 
 # Generates a line of WebIDL with the given spelling of the property name
 # (whether camelCase, _underscorePrefixed, etc.) and the given array of
 # extended attributes.
 def generateLine(propName, extendedAttrs):
     return "  [%s] attribute DOMString %s;\n" % (", ".join(extendedAttrs),
                                                  propName)
-def generate(output, idlFilename, preprocessorHeader):
-    cpp = list(buildconfig.substs['CPP'])
-    cpp += shellutil.split(buildconfig.substs['ACDEFINES'])
-    cpp.append(preprocessorHeader)
-    preprocessed = subprocess.check_output(cpp)
-
-    propList = eval(preprocessed)
+def generate(output, idlFilename, dataFile):
+    with open(dataFile, "r") as f:
+        propList = eval(f.read())
     props = ""
-    for [name, prop, id, flags, pref, proptype] in propList:
+    for name, prop, id, flags, pref, proptype in propList:
         if "CSS_PROPERTY_INTERNAL" in flags:
             continue
         # Unfortunately, even some of the getters here are fallible
         # (e.g. on nsComputedDOMStyle).
         extendedAttrs = ["CEReactions", "Throws", "TreatNullAs=EmptyString",
                          "SetterNeedsSubjectPrincipal=NonSystem"]
         if pref is not "":
             extendedAttrs.append('Pref="%s"' % pref)
--- a/dom/bindings/Makefile.in
+++ b/dom/bindings/Makefile.in
@@ -10,29 +10,16 @@ ifdef COMPILE_ENVIRONMENT
 include webidlsrcs.mk
 
 # These come from webidlsrcs.mk.
 # TODO Write directly into backend.mk (bug 1281618)
 CPPSRCS += $(globalgen_sources) $(unified_binding_cpp_files)
 
 include $(topsrcdir)/config/rules.mk
 
-# TODO This list should be emitted to a .pp file via
-# GenerateCSS2PropertiesWebIDL.py (bug 1281614)
-css2properties_dependencies = \
-  $(topsrcdir)/layout/style/nsCSSPropList.h \
-  $(topsrcdir)/layout/style/nsCSSPropAliasList.h \
-  $(webidl_base)/CSS2Properties.webidl.in \
-  $(topsrcdir)/layout/style/PythonCSSProps.h \
-  $(srcdir)/GenerateCSS2PropertiesWebIDL.py \
-  $(GLOBAL_DEPS) \
-  $(NULL)
-
-CSS2Properties.webidl: $(css2properties_dependencies)
-
 # Most of the logic for dependencies lives inside Python so it can be
 # used by multiple build backends. We simply have rules to generate
 # and include the .pp file.
 #
 # The generated .pp file contains all the important dependencies such as
 # changes to .webidl or .py files should result in code generation being
 # performed. But we do pull in file-lists.jon to catch file additions.
 codegen_dependencies := \
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -174,10 +174,10 @@ if CONFIG['CC_TYPE'] in ('clang', 'gcc')
     ]
 
 if CONFIG['COMPILE_ENVIRONMENT']:
     GENERATED_FILES += ['CSS2Properties.webidl']
     css_props = GENERATED_FILES['CSS2Properties.webidl']
     css_props.script = 'GenerateCSS2PropertiesWebIDL.py:generate'
     css_props.inputs = [
         '/dom/webidl/CSS2Properties.webidl.in',
-        '/layout/style/PythonCSSProps.h',
+        '!/layout/style/ServoCSSPropList.py',
     ]
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -587,17 +587,18 @@ static const char* sObserverTopics[] = {
   "last-pb-context-exited",
   "file-watcher-update",
 #ifdef ACCESSIBILITY
   "a11y-init-or-shutdown",
 #endif
   "cacheservice:empty-cache",
   "intl:app-locales-changed",
   "intl:requested-locales-changed",
-  "non-js-cookie-changed",
+  "cookie-changed",
+  "private-cookie-changed",
 };
 
 // PreallocateProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::PreallocateProcess()
 {
   RefPtr<ContentParent> process =
@@ -2935,29 +2936,34 @@ ContentParent::Observe(nsISupports* aSub
     LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales);
     Unused << SendUpdateAppLocales(appLocales);
   }
   else if (!strcmp(aTopic, "intl:requested-locales-changed")) {
     nsTArray<nsCString> requestedLocales;
     LocaleService::GetInstance()->GetRequestedLocales(requestedLocales);
     Unused << SendUpdateRequestedLocales(requestedLocales);
   }
-  else if (!strcmp(aTopic, "non-js-cookie-changed")) {
+  else if (!strcmp(aTopic, "cookie-changed") ||
+           !strcmp(aTopic, "private-cookie-changed")) {
     if (!aData) {
       return NS_ERROR_UNEXPECTED;
     }
     PNeckoParent *neckoParent = LoneManagedOrNullAsserts(ManagedPNeckoParent());
     if (!neckoParent) {
       return NS_OK;
     }
     PCookieServiceParent *csParent = LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent());
     if (!csParent) {
       return NS_OK;
     }
     auto *cs = static_cast<CookieServiceParent*>(csParent);
+    // Do not push these cookie updates to the same process they originated from.
+    if (cs->ProcessingCookie()) {
+      return NS_OK;
+    }
     if (!nsCRT::strcmp(aData, u"batch-deleted")) {
       nsCOMPtr<nsIArray> cookieList = do_QueryInterface(aSubject);
       NS_ASSERTION(cookieList, "couldn't get cookie list");
       cs->RemoveBatchDeletedCookies(cookieList);
       return NS_OK;
     }
 
     if (!nsCRT::strcmp(aData, u"cleared")) {
@@ -3344,18 +3350,20 @@ ContentParent::DeallocPNeckoParent(PNeck
   delete necko;
   return true;
 }
 
 PPrintingParent*
 ContentParent::AllocPPrintingParent()
 {
 #ifdef NS_PRINTING
-  MOZ_RELEASE_ASSERT(!mPrintingParent,
-                     "Only one PrintingParent should be created per process.");
+  if (mPrintingParent) {
+    // Only one PrintingParent should be created per process.
+    return nullptr;
+  }
 
   // Create the printing singleton for this process.
   mPrintingParent = new PrintingParent();
 
   // Take another reference for IPDL code.
   mPrintingParent.get()->AddRef();
 
   return mPrintingParent.get();
@@ -3703,16 +3711,19 @@ bool
 ContentParent::HasNotificationPermission(const IPC::Principal& aPrincipal)
 {
   return true;
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvShowAlert(nsIAlertNotification* aAlert)
 {
+  if (!aAlert) {
+    return IPC_FAIL_NO_REASON(this);
+  }
   nsCOMPtr<nsIPrincipal> principal;
   nsresult rv = aAlert->GetPrincipal(getter_AddRefs(principal));
   if (NS_WARN_IF(NS_FAILED(rv)) ||
       !HasNotificationPermission(IPC::Principal(principal))) {
 
       return IPC_OK();
   }
 
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -72,25 +72,31 @@ FFmpegDataDecoder<LIBAV_VER>::InitDecode
   mCodecContext->opaque = this;
 
   InitCodecContext();
 
   if (mExtraData) {
     mCodecContext->extradata_size = mExtraData->Length();
     // FFmpeg may use SIMD instructions to access the data which reads the
     // data in 32 bytes block. Must ensure we have enough data to read.
+#if LIBAVCODEC_VERSION_MAJOR >= 58
+    mExtraData->AppendElements(AV_INPUT_BUFFER_PADDING_SIZE);
+#else
     mExtraData->AppendElements(FF_INPUT_BUFFER_PADDING_SIZE);
+#endif
     mCodecContext->extradata = mExtraData->Elements();
   } else {
     mCodecContext->extradata_size = 0;
   }
 
+#if LIBAVCODEC_VERSION_MAJOR < 57
   if (codec->capabilities & CODEC_CAP_DR1) {
     mCodecContext->flags |= CODEC_FLAG_EMU_EDGE;
   }
+#endif
 
   if (mLib->avcodec_open2(mCodecContext, codec, nullptr) < 0) {
     mLib->avcodec_close(mCodecContext);
     mLib->av_freep(&mCodecContext);
     return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                        RESULT_DETAIL("Couldn't initialise ffmpeg decoder"));
   }
 
--- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
@@ -58,22 +58,24 @@ FFmpegLibWrapper::Link()
 
   enum {
     AV_FUNC_AVUTIL_MASK = 1 << 8,
     AV_FUNC_53 = 1 << 0,
     AV_FUNC_54 = 1 << 1,
     AV_FUNC_55 = 1 << 2,
     AV_FUNC_56 = 1 << 3,
     AV_FUNC_57 = 1 << 4,
+    AV_FUNC_58 = 1 << 5,
     AV_FUNC_AVUTIL_53 = AV_FUNC_53 | AV_FUNC_AVUTIL_MASK,
     AV_FUNC_AVUTIL_54 = AV_FUNC_54 | AV_FUNC_AVUTIL_MASK,
     AV_FUNC_AVUTIL_55 = AV_FUNC_55 | AV_FUNC_AVUTIL_MASK,
     AV_FUNC_AVUTIL_56 = AV_FUNC_56 | AV_FUNC_AVUTIL_MASK,
     AV_FUNC_AVUTIL_57 = AV_FUNC_57 | AV_FUNC_AVUTIL_MASK,
-    AV_FUNC_AVCODEC_ALL = AV_FUNC_53 | AV_FUNC_54 | AV_FUNC_55 | AV_FUNC_56 | AV_FUNC_57,
+    AV_FUNC_AVUTIL_58 = AV_FUNC_58 | AV_FUNC_AVUTIL_MASK,
+    AV_FUNC_AVCODEC_ALL = AV_FUNC_53 | AV_FUNC_54 | AV_FUNC_55 | AV_FUNC_56 | AV_FUNC_57 | AV_FUNC_58,
     AV_FUNC_AVUTIL_ALL = AV_FUNC_AVCODEC_ALL | AV_FUNC_AVUTIL_MASK
   };
 
   switch (macro) {
     case 53:
       version = AV_FUNC_53;
       break;
     case 54:
@@ -83,16 +85,19 @@ FFmpegLibWrapper::Link()
       version = AV_FUNC_55;
       break;
     case 56:
       version = AV_FUNC_56;
       break;
     case 57:
       version = AV_FUNC_57;
       break;
+    case 58:
+      version = AV_FUNC_58;
+      break;
     default:
       FFMPEG_LOG("Unknown avcodec version");
       Unlink();
       return isFFMpeg
              ? ((macro > 57)
                 ? LinkResult::UnknownFutureFFMpegVersion
                 : LinkResult::UnknownOlderFFMpegVersion)
              // All LibAV versions<54.35.1 are blocked, therefore we must be
@@ -131,19 +136,19 @@ FFmpegLibWrapper::Link()
   AV_FUNC(av_parser_close, AV_FUNC_AVCODEC_ALL)
   AV_FUNC(av_parser_parse2, AV_FUNC_AVCODEC_ALL)
   AV_FUNC(avcodec_alloc_frame, (AV_FUNC_53 | AV_FUNC_54))
   AV_FUNC(avcodec_get_frame_defaults, (AV_FUNC_53 | AV_FUNC_54))
   AV_FUNC(avcodec_free_frame, AV_FUNC_54)
   AV_FUNC(av_log_set_level, AV_FUNC_AVUTIL_ALL)
   AV_FUNC(av_malloc, AV_FUNC_AVUTIL_ALL)
   AV_FUNC(av_freep, AV_FUNC_AVUTIL_ALL)
-  AV_FUNC(av_frame_alloc, (AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57))
-  AV_FUNC(av_frame_free, (AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57))
-  AV_FUNC(av_frame_unref, (AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57))
+  AV_FUNC(av_frame_alloc, (AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 | AV_FUNC_AVUTIL_58))
+  AV_FUNC(av_frame_free, (AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 | AV_FUNC_AVUTIL_58))
+  AV_FUNC(av_frame_unref, (AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 | AV_FUNC_AVUTIL_58))
   AV_FUNC_OPTION(av_frame_get_colorspace, AV_FUNC_AVUTIL_ALL)
 #undef AV_FUNC
 #undef AV_FUNC_OPTION
 
   avcodec_register_all();
   if (MOZ_LOG_TEST(sPDMLog, LogLevel::Debug)) {
     av_log_set_level(AV_LOG_DEBUG);
   } else if (MOZ_LOG_TEST(sPDMLog, LogLevel::Info)) {
--- a/dom/media/platforms/ffmpeg/FFmpegLibs.h
+++ b/dom/media/platforms/ffmpeg/FFmpegLibs.h
@@ -22,18 +22,20 @@ extern "C" {
 #if LIBAVCODEC_VERSION_MAJOR < 55
 #define AV_CODEC_ID_VP6F CODEC_ID_VP6F
 #define AV_CODEC_ID_H264 CODEC_ID_H264
 #define AV_CODEC_ID_AAC CODEC_ID_AAC
 #define AV_CODEC_ID_MP3 CODEC_ID_MP3
 #define AV_CODEC_ID_VP8 CODEC_ID_VP8
 #define AV_CODEC_ID_NONE CODEC_ID_NONE
 #define AV_CODEC_ID_FLAC CODEC_ID_FLAC
+typedef CodecID AVCodecID;
+#endif
+#if LIBAVCODEC_VERSION_MAJOR <= 55
 #define AV_CODEC_FLAG_LOW_DELAY CODEC_FLAG_LOW_DELAY
-typedef CodecID AVCodecID;
 #endif
 
 #ifdef FFVPX_VERSION
 enum { LIBAV_VER = FFVPX_VERSION };
 #else
 enum { LIBAV_VER = LIBAVCODEC_VERSION_MAJOR };
 #endif
 
--- a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
@@ -21,22 +21,25 @@ template <int V> class FFmpegDecoderModu
 public:
   static already_AddRefed<PlatformDecoderModule> Create(FFmpegLibWrapper*);
 };
 
 static FFmpegLibWrapper sLibAV;
 
 static const char* sLibs[] = {
 #if defined(XP_DARWIN)
+  "libavcodec.58.dylib",
   "libavcodec.57.dylib",
   "libavcodec.56.dylib",
   "libavcodec.55.dylib",
   "libavcodec.54.dylib",
   "libavcodec.53.dylib",
 #else
+  "libavcodec.so.58",
+  "libavcodec-ffmpeg.so.58",
   "libavcodec-ffmpeg.so.57",
   "libavcodec-ffmpeg.so.56",
   "libavcodec.so.57",
   "libavcodec.so.56",
   "libavcodec.so.55",
   "libavcodec.so.54",
   "libavcodec.so.53",
 #endif
@@ -129,16 +132,17 @@ FFmpegRuntimeLinker::CreateDecoderModule
   }
   RefPtr<PlatformDecoderModule> module;
   switch (sLibAV.mVersion) {
     case 53: module = FFmpegDecoderModule<53>::Create(&sLibAV); break;
     case 54: module = FFmpegDecoderModule<54>::Create(&sLibAV); break;
     case 55:
     case 56: module = FFmpegDecoderModule<55>::Create(&sLibAV); break;
     case 57: module = FFmpegDecoderModule<57>::Create(&sLibAV); break;
+    case 58: module = FFmpegDecoderModule<58>::Create(&sLibAV); break;
     default: module = nullptr;
   }
   return module.forget();
 }
 
 /* static */ const char*
 FFmpegRuntimeLinker::LinkStatusString()
 {
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
@@ -161,17 +161,17 @@ FFmpegVideoDecoder<LIBAV_VER>::InitCodec
     decode_threads = 8;
   } else if (mInfo.mDisplay.width >= 1024) {
     decode_threads = 4;
   } else if (mInfo.mDisplay.width >= 320) {
     decode_threads = 2;
   }
 
   if (mLowLatency) {
-    mCodecContext->flags |= CODEC_FLAG_LOW_DELAY;