Backed out changeset c34fe673bb97 (bug 1014185) for perma failures in browser_bug1163570.js
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 19 Feb 2016 17:19:19 +0100
changeset 321160 69ec3dc408a2a720cb2b8210fea33e3504aeec22
parent 321159 a87d6d52c1fcfffbd6f44537a9671973802cca13
child 321210 ad09ca2efd069c8ee617e8a67cfa76b962e286ce
child 321292 15d6b3343104ce28acbd85ed0aa5db3ebab38b3b
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1014185, 1163570
milestone47.0a1
backs outc34fe673bb97d511920d2986cb84057f62e0c4a0
first release with
nightly linux32
69ec3dc408a2 / 47.0a1 / 20160220030407 / files
nightly linux64
69ec3dc408a2 / 47.0a1 / 20160220030407 / files
nightly mac
69ec3dc408a2 / 47.0a1 / 20160220030407 / files
nightly win32
69ec3dc408a2 / 47.0a1 / 20160220030407 / files
nightly win64
69ec3dc408a2 / 47.0a1 / 20160220030407 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset c34fe673bb97 (bug 1014185) for perma failures in browser_bug1163570.js
browser/base/content/browser-social.js
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
browser/components/about/AboutRedirector.cpp
browser/components/build/nsModule.cpp
browser/components/customizableui/CustomizeMode.jsm
browser/components/customizableui/content/aboutCustomizing.xul
browser/components/customizableui/content/jar.mn
browser/components/customizableui/content/panelUI.js
browser/components/customizableui/test/browser.ini
browser/components/customizableui/test/browser_889120_customize_tab_merging.js
browser/components/nsBrowserGlue.js
browser/components/sessionstore/SessionStore.jsm
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/browser.properties
browser/modules/CustomizationTabPreloader.jsm
browser/modules/moz.build
dom/plugins/test/mochitest/browser_bug1163570.js
testing/mochitest/browser-test.js
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -254,16 +254,18 @@ SocialUI = {
         node.setAttribute("disabled", "true")
       }
     }
   },
 
   // called on tab/urlbar/location changes and after customization. Update
   // anything that is tab specific.
   updateState: function() {
+    if (location == "about:customizing")
+      return;
     goSetCommandEnabled("Social:PageShareOrMark", this.canShareOrMarkPage(gBrowser.currentURI));
     if (!SocialUI.enabled)
       return;
     // larger update that may change button icons
     SocialMarks.update();
   }
 }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -178,16 +178,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ProcessHangMonitor",
   "resource:///modules/ProcessHangMonitor.jsm");
 
 if (AppConstants.MOZ_SAFE_BROWSING) {
   XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
     "resource://gre/modules/SafeBrowsing.jsm");
 }
 
+XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader",
+  "resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader");
+
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Translation",
   "resource:///modules/translation/Translation.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
   "resource:///modules/SitePermissions.jsm");
@@ -4331,17 +4334,17 @@ var XULBrowserWindow = {
       } else {
         this.reloadCommand.removeAttribute("disabled");
       }
 
       if (gURLBar) {
         URLBarSetURI(aLocationURI);
 
         BookmarkingUI.onLocationChange();
-        SocialUI.updateState();
+        SocialUI.updateState(location);
         UITour.onLocationChange(location);
         gTabletModePageCounter.inc();
       }
 
       // Utility functions for disabling find
       var shouldDisableFind = function shouldDisableFind(aDocument) {
         let docElt = aDocument.documentElement;
         return docElt && docElt.getAttribute("disablefastfind") == "true";
@@ -4379,21 +4382,22 @@ var XULBrowserWindow = {
             content.document.addEventListener("readystatechange", onContentRSChange);
           }
         }
       } else
         disableFindCommands(false);
 
       // Try not to instantiate gCustomizeMode as much as possible,
       // so don't use CustomizeMode.jsm to check for URI or customizing.
-      if (location == "about:blank" &&
-          gBrowser.selectedTab.hasAttribute("customizemode")) {
+      let customizingURI = "about:customizing";
+      if (location == customizingURI) {
         gCustomizeMode.enter();
-      } else if (CustomizationHandler.isEnteringCustomizeMode ||
-                 CustomizationHandler.isCustomizing()) {
+      } else if (location != customizingURI &&
+                 (CustomizationHandler.isEnteringCustomizeMode ||
+                  CustomizationHandler.isCustomizing())) {
         gCustomizeMode.exit();
       }
     }
     UpdateBackForwardCommands(gBrowser.webNavigation);
     ReaderParent.updateReaderButton(gBrowser.selectedBrowser);
 
     gGestureSupport.restoreRotationState();
 
@@ -6381,19 +6385,16 @@ function undoCloseWindow(aIndex) {
 /*
  * Determines if a tab is "empty", usually used in the context of determining
  * if it's ok to close the tab.
  */
 function isTabEmpty(aTab) {
   if (aTab.hasAttribute("busy"))
     return false;
 
-  if (aTab.hasAttribute("customizemode"))
-    return false;
-
   let browser = aTab.linkedBrowser;
   if (!isBlankPageURL(browser.currentURI.spec))
     return false;
 
   if (browser.hasContentOpener)
     return false;
 
   if (browser.canGoForward || browser.canGoBack)
@@ -7286,16 +7287,17 @@ var gRemoteTabsUI = {
  *        the one from the new URI.
  * @return True if an existing tab was found, false otherwise
  */
 function switchToTabHavingURI(aURI, aOpenNew, aOpenParams={}) {
   // Certain URLs can be switched to irrespective of the source or destination
   // window being in private browsing mode:
   const kPrivateBrowsingWhitelist = new Set([
     "about:addons",
+    "about:customizing",
   ]);
 
   let ignoreFragment = aOpenParams.ignoreFragment;
   let ignoreQueryString = aOpenParams.ignoreQueryString;
   let replaceQueryString = aOpenParams.replaceQueryString;
 
   // These properties are only used by switchToTabHavingURI and should
   // not be used as a parameter for the new load.
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1393,21 +1393,16 @@
                   var characterSet = browser.characterSet;
                   const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
                                                  .getService(Components.interfaces.nsITextToSubURI);
                   title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
                 } catch(ex) { /* Do nothing. */ }
 
                 crop = "center";
 
-              } else if (aTab.hasAttribute("customizemode")) {
-                let brandBundle = document.getElementById("bundle_brand");
-                let brandShortName = brandBundle.getString("brandShortName");
-                title = gNavigatorBundle.getFormattedString("customizeMode.tabTitle",
-                                                            [ brandShortName ]);
               } else // Still no title?  Fall back to our untitled string.
                 title = this.mStringBundle.getString("tabs.emptyTabTitle");
             }
 
             if (aTab.label == title &&
                 aTab.crop == crop)
               return false;
 
@@ -1926,16 +1921,21 @@
                                      .createInstance(Components.interfaces.nsIWebProgress);
             filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
             b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
             this._tabListeners.set(t, tabListener);
             this._tabFilters.set(t, filter);
 
             b.droppedLinkHandler = handleDroppedLink;
 
+            // Swap in a preloaded customize tab, if available.
+            if (aURI == "about:customizing") {
+              usingPreloadedContent = gCustomizationTabPreloader.newTab(t);
+            }
+
             // Dispatch a new tab notification.  We do this once we're
             // entirely done, so that things are in a consistent state
             // even if the event listener opens or closes tabs.
             var detail = aEventDetail || {};
             var evt = new CustomEvent("TabOpen", { bubbles: true, detail });
             t.dispatchEvent(evt);
 
             // If we didn't swap docShells with a preloaded browser
@@ -2469,16 +2469,43 @@
               } while (tab && remainingTabs.indexOf(tab) == -1);
             }
 
             this.selectedTab = tab;
           ]]>
         </body>
       </method>
 
+      <method name="swapNewTabWithBrowser">
+        <parameter name="aNewTab"/>
+        <parameter name="aBrowser"/>
+        <body>
+          <![CDATA[
+            // The browser must be standalone.
+            if (aBrowser.getTabBrowser())
+              throw Cr.NS_ERROR_INVALID_ARG;
+
+            // The tab is definitely not loading.
+            aNewTab.removeAttribute("busy");
+            if (aNewTab.selected) {
+              this.mIsBusy = false;
+            }
+
+            this._swapBrowserDocShells(aNewTab, aBrowser);
+
+            // Update the new tab's title.
+            this.setTabTitle(aNewTab);
+
+            if (aNewTab.selected) {
+              this.updateCurrentBrowser(true);
+            }
+          ]]>
+        </body>
+      </method>
+
       <method name="swapBrowsersAndCloseOther">
         <parameter name="aOurTab"/>
         <parameter name="aOtherTab"/>
         <body>
           <![CDATA[
             // Do not allow transfering a private tab to a non-private window
             // and vice versa.
             if (PrivateBrowsingUtils.isWindowPrivate(window) !=
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -94,16 +94,18 @@ static RedirEntry kRedirMap[] = {
   { "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
     nsIAboutModule::ALLOW_SCRIPT },
 #ifdef MOZ_SERVICES_HEALTHREPORT
   { "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
 #endif
   { "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
+  { "customizing", "chrome://browser/content/customizableui/aboutCustomizing.xul",
+    nsIAboutModule::ALLOW_SCRIPT },
   { "loopconversation", "chrome://loop/content/panels/conversation.html",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT |
     nsIAboutModule::ENABLE_INDEXED_DB },
   { "looppanel", "chrome://loop/content/panels/panel.html",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
--- a/browser/components/build/nsModule.cpp
+++ b/browser/components/build/nsModule.cpp
@@ -102,16 +102,17 @@ static const mozilla::Module::ContractID
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "home", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newtab", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "accounts", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
 #ifdef MOZ_SERVICES_HEALTHREPORT
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
 #endif
+    { NS_ABOUT_MODULE_CONTRACTID_PREFIX "customizing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "looppanel", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "loopconversation", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "reader", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
 #if defined(XP_WIN)
     { NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
 #elif defined(XP_MACOSX)
     { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
 #endif
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -6,16 +6,17 @@
 
 this.EXPORTED_SYMBOLS = ["CustomizeMode"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 const kPrefCustomizationDebug = "browser.uiCustomization.debug";
 const kPrefCustomizationAnimation = "browser.uiCustomization.disableAnimation";
 const kPaletteId = "customization-palette";
+const kAboutURI = "about:customizing";
 const kDragDataTypePrefix = "text/toolbarwrapper-id/";
 const kPlaceholderClass = "panel-customization-placeholder";
 const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
 const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
 const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
 const kMaxTransitionDurationMs = 2000;
 
 const kPanelItemContextMenu = "customizationPanelItemContextMenu";
@@ -30,18 +31,16 @@ Cu.import("resource://gre/modules/AddonM
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager",
                                   "resource:///modules/DragPositionManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
                                   "resource:///modules/BrowserUITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
-                                  "resource:///modules/sessionstore/SessionStore.jsm");
 
 let gDebug;
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
   let ConsoleAPI = scope.ConsoleAPI;
   try {
     gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
@@ -52,33 +51,16 @@ XPCOMUtils.defineLazyGetter(this, "log",
   };
   return new scope.ConsoleAPI(consoleOptions);
 });
 
 var gDisableAnimation = null;
 
 var gDraggingInToolbars;
 
-var gTab;
-
-function closeGlobalTab() {
-  let win = gTab.ownerGlobal;
-  if (win.gBrowser.browsers.length == 1) {
-    win.BrowserOpenTab();
-  }
-  win.gBrowser.removeTab(gTab);
-}
-
-function unregisterGlobalTab() {
-  gTab.removeEventListener("TabClose", unregisterGlobalTab);
-  gTab.ownerGlobal.removeEventListener("unload", unregisterGlobalTab);
-  gTab.removeAttribute("customizemode");
-  gTab = null;
-}
-
 function CustomizeMode(aWindow) {
   if (gDisableAnimation === null) {
     gDisableAnimation = Services.prefs.getPrefType(kPrefCustomizationAnimation) == Ci.nsIPrefBranch.PREF_BOOL &&
                         Services.prefs.getBoolPref(kPrefCustomizationAnimation);
   }
   this.window = aWindow;
   this.document = aWindow.document;
   this.browser = aWindow.gBrowser;
@@ -153,67 +135,37 @@ CustomizeMode.prototype = {
    let lwthemeIcon = aDocument.getAnonymousElementByAttribute(lwthemeButton,
           "class", "button-icon");
    let imageURL = LightweightThemeManager.currentTheme === null ?
           "chrome://browser/skin/theme-switcher-icon.png" :
           LightweightThemeManager.currentTheme.iconURL;
     lwthemeIcon.style.backgroundImage = "url(" + imageURL + ")";
   },
 
-  setTab: function(aTab) {
-    if (gTab) {
-      closeGlobalTab();
-    }
-
-    gTab = aTab;
-
-    gTab.setAttribute("customizemode", "true");
-    SessionStore.persistTabAttribute("customizemode");
-
-    gTab.linkedBrowser.stop();
-
-    let win = gTab.ownerGlobal;
-
-    win.gBrowser.setTabTitle(gTab);
-    win.gBrowser.setIcon(gTab,
-                         "chrome://browser/skin/customizableui/customizeFavicon.ico");
-
-    gTab.addEventListener("TabClose", unregisterGlobalTab);
-    win.addEventListener("unload", unregisterGlobalTab);
-
-    if (gTab.selected) {
-      win.gCustomizeMode.enter();
-    }
-  },
-
   enter: function() {
     this._wantToBeInCustomizeMode = true;
 
     if (this._customizing || this._handler.isEnteringCustomizeMode) {
       return;
     }
 
     // Exiting; want to re-enter once we've done that.
     if (this._handler.isExitingCustomizeMode) {
       log.debug("Attempted to enter while we're in the middle of exiting. " +
                 "We'll exit after we've entered");
       return;
     }
 
-    if (!gTab) {
-      this.setTab(this.browser.loadOneTab("about:blank",
-                                          { inBackground: false,
-                                            skipAnimation: true }));
-      return;
-    }
-    if (!gTab.selected) {
-      gTab.ownerGlobal.gBrowser.selectedTab = gTab;
-    }
-    gTab.ownerGlobal.focus();
-    if (gTab.ownerDocument != this.document) {
+
+    // We don't need to switch to kAboutURI, or open a new tab at
+    // kAboutURI if we're already on it.
+    if (this.browser.selectedBrowser.currentURI.spec != kAboutURI) {
+      this.window.switchToTabHavingURI(kAboutURI, true, {
+        skipTabAnimation: true,
+      });
       return;
     }
 
     let window = this.window;
     let document = this.document;
 
     this._handler.isEnteringCustomizeMode = true;
 
@@ -356,16 +308,20 @@ CustomizeMode.prototype = {
 
       CustomizableUI.dispatchToolboxEvent("customizationready", {}, window);
       this._enableOutlinesTimeout = window.setTimeout(() => {
         this.document.getElementById("nav-bar").setAttribute("showoutline", "true");
         this.panelUIContents.setAttribute("showoutline", "true");
         delete this._enableOutlinesTimeout;
       }, 0);
 
+      // It's possible that we didn't enter customize mode via the menu panel,
+      // meaning we didn't kick off about:customizing preloading. If that's
+      // the case, let's kick it off for the next time we load this mode.
+      window.gCustomizationTabPreloader.ensurePreloading();
       if (!this._wantToBeInCustomizeMode) {
         this.exit();
       }
     }.bind(this)).then(null, function(e) {
       log.error("Error entering customize mode", e);
       // We should ensure this has been called, and calling it again doesn't hurt:
       window.PanelUI.endBatchUpdate();
       this._handler.isEnteringCustomizeMode = false;
@@ -441,24 +397,41 @@ CustomizeMode.prototype = {
     Task.spawn(function*() {
       yield this.depopulatePalette();
 
       yield this._doTransition(false);
       this.removeLWTStyling();
 
       Services.obs.removeObserver(this, "lightweight-theme-window-updated", false);
 
-      if (this.browser.selectedTab == gTab) {
-        if (gTab.linkedBrowser.currentURI.spec == "about:blank") {
-          closeGlobalTab();
+      let browser = document.getElementById("browser");
+      if (this.browser.selectedBrowser.currentURI.spec == kAboutURI) {
+        let custBrowser = this.browser.selectedBrowser;
+        if (custBrowser.canGoBack) {
+          // If there's history to this tab, just go back.
+          // Note that this throws an exception if the previous document has a
+          // problematic URL (e.g. about:idontexist)
+          try {
+            custBrowser.goBack();
+          } catch (ex) {
+            log.error(ex);
+          }
         } else {
-          unregisterGlobalTab();
+          // If we can't go back, we're removing the about:customization tab.
+          // We only do this if we're the top window for this window (so not
+          // a dialog window, for example).
+          if (window.getTopWin(true) == window) {
+            let customizationTab = this.browser.selectedTab;
+            if (this.browser.browsers.length == 1) {
+              window.BrowserOpenTab();
+            }
+            this.browser.removeTab(customizationTab);
+          }
         }
       }
-      let browser = document.getElementById("browser");
       browser.parentNode.selectedPanel = browser;
       let customizer = document.getElementById("customization-container");
       customizer.hidden = true;
 
       window.gNavToolbox.removeEventListener("toolbarvisibilitychange", this);
 
       DragPositionManager.stop();
       this._removeDragHandlers(this.visiblePalette);
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/content/aboutCustomizing.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE window [
+  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+  %brandDTD;
+  <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+  %browserDTD;
+]>
+
+<window id="aboutCustomizingWindow"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        title="&customizeMode.tabTitle;">
+  <html:head>
+    <html:link rel="icon" type="image/x-icon"
+               href="chrome://browser/skin/customizableui/customizeFavicon.ico"/>
+  </html:head>
+</window>
--- a/browser/components/customizableui/content/jar.mn
+++ b/browser/components/customizableui/content/jar.mn
@@ -1,10 +1,11 @@
 # 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/.
 
 browser.jar:
+  content/browser/customizableui/aboutCustomizing.xul
   content/browser/customizableui/panelUI.css
   content/browser/customizableui/panelUI.js
   content/browser/customizableui/panelUI.xml
   content/browser/customizableui/toolbar.xml
 
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -152,16 +152,20 @@ const PanelUI = {
           aEvent.type == "command") {
         anchor = this.menuButton;
       } else {
         anchor = aEvent.target;
       }
 
       this.panel.addEventListener("popupshown", function onPopupShown() {
         this.removeEventListener("popupshown", onPopupShown);
+        // As an optimization for the customize mode transition, we preload
+        // about:customizing in the background once the menu panel is first
+        // shown.
+        gCustomizationTabPreloader.ensurePreloading();
         deferred.resolve();
       });
 
       let iconAnchor =
         document.getAnonymousElementByAttribute(anchor, "class",
                                                 "toolbarbutton-icon");
       this.panel.openPopup(iconAnchor || anchor);
     }, (reason) => {
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -20,16 +20,17 @@ skip-if = os == "linux"
 [browser_885052_customize_mode_observers_disabed.js]
 # Bug 951403 - Disabled on OSX for frequent failures
 skip-if = os == "mac"
 
 [browser_885530_showInPrivateBrowsing.js]
 [browser_886323_buildArea_removable_nodes.js]
 [browser_887438_currentset_shim.js]
 [browser_888817_currentset_updating.js]
+[browser_889120_customize_tab_merging.js]
 [browser_890140_orphaned_placeholders.js]
 [browser_890262_destroyWidget_after_add_to_panel.js]
 [browser_892955_isWidgetRemovable_for_removed_widgets.js]
 [browser_892956_destroyWidget_defaultPlacements.js]
 [browser_909779_overflow_toolbars_new_window.js]
 skip-if = os == "linux"
 
 [browser_901207_searchbar_in_panel.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_889120_customize_tab_merging.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const kTestToolbarId = "test-empty-drag";
+
+// Attempting to switch quickly from one tab to another to see whether the state changes
+// correctly.
+add_task(function* CheckBasicCustomizeMode() {
+  yield startCustomizing();
+  ok(CustomizationHandler.isCustomizing(), "We should be in customize mode");
+  yield endCustomizing();
+  ok(!CustomizationHandler.isCustomizing(), "We should not be in customize mode");
+});
+add_task(function* CheckQuickCustomizeModeSwitch() {
+  let tab1 = gBrowser.addTab("about:newtab");
+  gBrowser.selectedTab = tab1;
+  let tab2 = gBrowser.addTab("about:customizing");
+  let tab3 = gBrowser.addTab("about:newtab");
+  gBrowser.selectedTab = tab2;
+  try {
+    yield waitForCondition(() => CustomizationHandler.isEnteringCustomizeMode);
+  } catch (ex) {
+    Cu.reportError(ex);
+  }
+  ok(CustomizationHandler.isEnteringCustomizeMode, "Should be entering customize mode");
+  gBrowser.selectedTab = tab3;
+  try {
+    yield waitForCondition(() => !CustomizationHandler.isEnteringCustomizeMode && !CustomizationHandler.isCustomizing());
+  } catch (ex) {
+    Cu.reportError(ex);
+  }
+  ok(!CustomizationHandler.isCustomizing(), "Should not be entering customize mode");
+  gBrowser.removeTab(tab1);
+  gBrowser.removeTab(tab2);
+  gBrowser.removeTab(tab3);
+});
+
+add_task(function* asyncCleanup() {
+  yield endCustomizing();
+});
+
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -53,16 +53,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/BookmarkJSONUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebappManager",
                                   "resource:///modules/WebappManager.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
                                   "resource://gre/modules/PageThumbs.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader",
+                                  "resource:///modules/CustomizationTabPreloader.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "PdfJs",
                                   "resource://pdf.js/PdfJs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ProcessHangMonitor",
                                   "resource:///modules/ProcessHangMonitor.jsm");
 
 if (AppConstants.NIGHTLY_BUILD) {
   XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils",
@@ -1064,16 +1067,17 @@ BrowserGlue.prototype = {
                          .getService(Ci.nsIAppStartup);
       appStartup.trackStartupCrashEnd();
     } catch (e) {
       Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
     }
 
     SelfSupportBackend.uninit();
 
+    CustomizationTabPreloader.uninit();
     WebappManager.uninit();
 
     NewTabPrefsProvider.prefs.uninit();
     AboutNewTab.uninit();
     webrtcUI.uninit();
     FormValidationHandler.uninit();
     if (AppConstants.NIGHTLY_BUILD) {
       AddonWatcher.uninit();
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -796,18 +796,16 @@ var SessionStoreInternal = {
         if (activePageData) {
           if (activePageData.title) {
             tab.label = activePageData.title;
             tab.crop = "end";
           } else if (activePageData.url != "about:blank") {
             tab.label = activePageData.url;
             tab.crop = "center";
           }
-        } else if (tab.hasAttribute("customizemode")) {
-          win.gCustomizeMode.setTab(tab);
         }
 
         // Restore the tab icon.
         if ("image" in tabData) {
           // Using null as the loadingPrincipal because serializing
           // the principal would be overkill. Within SetIcon we
           // default to the systemPrincipal if aLoadingPrincipal is
           // null which will allow the favicon to load.
@@ -3296,20 +3294,16 @@ var SessionStoreInternal = {
    * Kicks off restoring the given tab.
    *
    * @param aTab
    *        the tab to restore
    * @param aLoadArguments
    *        optional load arguments used for loadURI()
    */
   restoreTabContent: function (aTab, aLoadArguments = null) {
-    if (aTab.hasAttribute("customizemode")) {
-      return;
-    }
-
     let browser = aTab.linkedBrowser;
     let window = aTab.ownerDocument.defaultView;
     let tabbrowser = window.gBrowser;
     let tabData = TabState.clone(aTab);
     let activeIndex = tabData.index - 1;
     let activePageData = tabData.entries[activeIndex] || null;
     let uri = activePageData ? activePageData.url || null : null;
     if (aLoadArguments) {
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -839,16 +839,17 @@ you can use these alternative items. Oth
 <!ENTITY social.closeNotificationItem.label "Not Now">
 
 <!ENTITY social.directory.label "Activations Directory">
 <!ENTITY social.directory.text "You can activate Share services from the directory.">
 <!ENTITY social.directory.button "Take me there!">
 <!ENTITY social.directory.introText "Click on a service to add it to &brandShortName;.">
 <!ENTITY social.directory.viewmore.text "View More">
 
+<!ENTITY customizeMode.tabTitle "Customize &brandShortName;">
 <!ENTITY customizeMode.menuAndToolbars.header2 "Additional Tools and Features">
 <!ENTITY customizeMode.menuAndToolbars.empty "Want more tools?">
 <!ENTITY customizeMode.menuAndToolbars.emptyLink "Choose from thousands of add-ons">
 <!ENTITY customizeMode.restoreDefaults "Restore Defaults">
 <!ENTITY customizeMode.toolbars "Show / Hide Toolbars">
 <!ENTITY customizeMode.titlebar "Title Bar">
 <!ENTITY customizeMode.lwthemes "Themes">
 <!ENTITY customizeMode.lwthemes.myThemes "My Themes">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -684,19 +684,16 @@ flashHang.helpButton.accesskey = L
 
 # LOCALIZATION NOTE(customizeTips.tip0): %1$S will be replaced with the text defined
 # in customizeTips.tip0.hint, %2$S will be replaced with brandShortName, %3$S will
 # be replaced with a hyperlink containing the text defined in customizeTips.tip0.learnMore.
 customizeTips.tip0 = %1$S: You can customize %2$S to work the way you do. Simply drag any of the above to the menu or toolbar. %3$S about customizing %2$S.
 customizeTips.tip0.hint = Hint
 customizeTips.tip0.learnMore = Learn more
 
-# LOCALIZATION NOTE (customizeMode.tabTitle): %S is brandShortName
-customizeMode.tabTitle = Customize %S
-
 # LOCALIZATION NOTE(appmenu.*.description, appmenu.*.label): these are used for
 # the appmenu labels and buttons that appear when an update is staged for
 # installation or a background update has failed and a manual download is required.
 # %S is brandShortName
 appmenu.restartNeeded.description = Restart %S to apply updates
 appmenu.updateFailed.description = Background update failed, please download update
 appmenu.restartBrowserButton.label = Restart %S
 appmenu.downloadUpdateButton.label = Download Update
new file mode 100644
--- /dev/null
+++ b/browser/modules/CustomizationTabPreloader.jsm
@@ -0,0 +1,173 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["CustomizationTabPreloader"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "HiddenFrame",
+  "resource:///modules/HiddenFrame.jsm");
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>";
+const CUSTOMIZATION_URL = "about:customizing";
+
+// The interval between swapping in a preload docShell and kicking off the
+// next preload in the background.
+const PRELOADER_INTERVAL_MS = 600;
+
+function createTimer(obj, delay) {
+  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  timer.init(obj, delay, Ci.nsITimer.TYPE_ONE_SHOT);
+  return timer;
+}
+
+function clearTimer(timer) {
+  if (timer) {
+    timer.cancel();
+  }
+  return null;
+}
+
+this.CustomizationTabPreloader = {
+  uninit: function () {
+    CustomizationTabPreloaderInternal.uninit();
+  },
+
+  newTab: function (aTab) {
+    return CustomizationTabPreloaderInternal.newTab(aTab);
+  },
+
+  /**
+   * ensurePreloading starts the preloading of the about:customizing
+   * content page. This function is idempotent (until a call to uninit),
+   * so multiple calls to it are fine.
+   */
+  ensurePreloading: function() {
+    CustomizationTabPreloaderInternal.ensurePreloading();
+  },
+};
+
+Object.freeze(CustomizationTabPreloader);
+
+this.CustomizationTabPreloaderInternal = {
+  _browser: null,
+
+  uninit: function () {
+    if (this._browser) {
+      this._browser.destroy();
+      this._browser = null;
+    }
+  },
+
+  newTab: function (aTab) {
+    let win = aTab.ownerDocument.defaultView;
+    if (win.gBrowser && this._browser) {
+      return this._browser.swapWithNewTab(aTab);
+    }
+
+    return false;
+  },
+
+  ensurePreloading: function () {
+    if (!this._browser) {
+      this._browser = new HiddenBrowser();
+    }
+  }
+};
+
+function HiddenBrowser() {
+  this._createBrowser();
+}
+
+HiddenBrowser.prototype = {
+  _timer: null,
+  _hiddenFrame: null,
+
+  get isPreloaded() {
+    return this._browser &&
+           this._browser.contentDocument &&
+           this._browser.contentDocument.readyState === "complete" &&
+           this._browser.currentURI.spec === CUSTOMIZATION_URL;
+  },
+
+  swapWithNewTab: function (aTab) {
+    if (!this.isPreloaded || this._timer) {
+      return false;
+    }
+
+    let win = aTab.ownerDocument.defaultView;
+    let tabbrowser = win.gBrowser;
+
+    if (!tabbrowser) {
+      return false;
+    }
+
+    // Swap docShells.
+    tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
+
+    // Load all default frame scripts attached to the target window.
+    let mm = aTab.linkedBrowser.messageManager;
+    let scripts = win.getGroupMessageManager("browsers").getDelayedFrameScripts();
+    Array.forEach(scripts, ([script, runGlobal]) => mm.loadFrameScript(script, true, runGlobal));
+
+    // Remove the browser, it will be recreated by a timer.
+    this._removeBrowser();
+
+    // Start a timer that will kick off preloading the next page.
+    this._timer = createTimer(this, PRELOADER_INTERVAL_MS);
+
+    // Signal that we swapped docShells.
+    return true;
+  },
+
+  observe: function () {
+    this._timer = null;
+
+    // Start pre-loading the customization page.
+    this._createBrowser();
+  },
+
+  destroy: function () {
+    this._removeBrowser();
+    if (this._hiddenFrame) {
+      this._hiddenFrame.destroy();
+      this._hiddenFrame = null;
+    }
+    this._timer = clearTimer(this._timer);
+  },
+
+  _createBrowser: function () {
+    if (!this._hiddenFrame) {
+      this._hiddenFrame = new HiddenFrame();
+    }
+
+    this._hiddenFrame.get().then(aFrame => {
+      let doc = aFrame.document;
+      this._browser = doc.createElementNS(XUL_NS, "browser");
+      this._browser.permanentKey = {};
+      this._browser.setAttribute("type", "content");
+      this._browser.setAttribute("src", CUSTOMIZATION_URL);
+      this._browser.style.width = "400px";
+      this._browser.style.height = "400px";
+      doc.getElementById("win").appendChild(this._browser);
+    });
+  },
+
+  _removeBrowser: function () {
+    if (this._browser) {
+      this._browser.remove();
+      this._browser = null;
+    }
+  }
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -17,16 +17,17 @@ EXTRA_JS_MODULES += [
     'CastingApps.jsm',
     'Chat.jsm',
     'ContentClick.jsm',
     'ContentCrashHandlers.jsm',
     'ContentLinkHandler.jsm',
     'ContentObservers.jsm',
     'ContentSearch.jsm',
     'ContentWebRTC.jsm',
+    'CustomizationTabPreloader.jsm',
     'DirectoryLinksProvider.jsm',
     'E10SUtils.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'HiddenFrame.jsm',
     'LaterRun.jsm',
     'NetworkPrioritizer.jsm',
--- a/dom/plugins/test/mochitest/browser_bug1163570.js
+++ b/dom/plugins/test/mochitest/browser_bug1163570.js
@@ -34,72 +34,85 @@ function promiseWaitForEvent(object, eve
       resolve(event);
     }
     object.addEventListener(eventName, listener, capturing, chrome);
   });
 }
 
 add_task(function* () {
   registerCleanupFunction(function () {
+    Services.prefs.clearUserPref("browser.uiCustomization.disableAnimation");
     window.focus();
   });
 });
 
 add_task(function* () {
+  Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", true);
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
 
   let pluginTab = gBrowser.selectedTab = gBrowser.addTab();
-  let prefTab = gBrowser.addTab();
+  let customizeTab = gBrowser.addTab();
 
   yield promiseTabLoad(pluginTab, gTestRoot + "plugin_test.html");
-  yield promiseTabLoad(prefTab, "about:preferences");
+  yield promiseTabLoad(customizeTab, "about:customizing");
 
   let result = yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
     let doc = content.document;
     let plugin = doc.getElementById("testplugin");
     return !!plugin;
   });
 
   is(result, true, "plugin is loaded");
 
+  let cpromise = promiseWaitForEvent(window.gNavToolbox, "customizationready");
   let ppromise = promiseWaitForEvent(window, "MozAfterPaint");
-  gBrowser.selectedTab = prefTab;
+  gBrowser.selectedTab = customizeTab;
+  yield cpromise;
   yield ppromise;
 
   // We're going to switch tabs using actual mouse clicks, which helps
   // reproduce this bug.
   let tabStripContainer = document.getElementById("tabbrowser-tabs");
 
   // diagnosis if front end layout changes
   info("-> " + tabStripContainer.tagName); // tabs
   info("-> " + tabStripContainer.firstChild.tagName); // tab
   info("-> " + tabStripContainer.childNodes[0].label); // test harness tab
   info("-> " + tabStripContainer.childNodes[1].label); // plugin tab
-  info("-> " + tabStripContainer.childNodes[2].label); // preferences tab
+  info("-> " + tabStripContainer.childNodes[2].label); // customize tab
 
   for (let iteration = 0; iteration < 5; iteration++) {
+    cpromise = promiseWaitForEvent(window.gNavToolbox, "aftercustomization");
     ppromise = promiseWaitForEvent(window, "MozAfterPaint");
     EventUtils.synthesizeMouseAtCenter(tabStripContainer.childNodes[1], {}, window);
+    yield cpromise;
     yield ppromise;
 
     result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
       let doc = content.document;
       let plugin = doc.getElementById("testplugin");
       return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
     });
 
     is(result, true, "plugin is visible");
 
+    cpromise = promiseWaitForEvent(window.gNavToolbox, "customizationready");
     ppromise = promiseWaitForEvent(window, "MozAfterPaint");
     EventUtils.synthesizeMouseAtCenter(tabStripContainer.childNodes[2], {}, window);
+    yield cpromise;
     yield ppromise;
 
     result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
       let doc = content.document;
       let plugin = doc.getElementById("testplugin");
       return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
     });
     is(result, false, "plugin is hidden");
   }
 
-  gBrowser.removeTab(prefTab);
+  // wait for customize view to shutdown cleanly otherwise we get
+  // a ton of error spew on shutdown.
+  cpromise = promiseWaitForEvent(window.gNavToolbox, "aftercustomization");
+  gBrowser.removeTab(customizeTab);
+  yield cpromise;
+
   gBrowser.removeTab(pluginTab);
 });
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -3,16 +3,19 @@
 var gTimeoutSeconds = 45;
 var gConfig;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader",
+  "resource:///modules/CustomizationTabPreloader.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
   "resource:///modules/ContentSearch.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SelfSupportBackend",
   "resource:///modules/SelfSupportBackend.jsm");
 
 const SIMPLETEST_OVERRIDES =
   ["ok", "is", "isnot", "todo", "todo_is", "todo_isnot", "info", "expectAssertions", "requestCompleteLog"];
@@ -611,16 +614,17 @@ Tester.prototype = {
 
             // Do the same for the social sidebar.
             let socialSidebar = document.getElementById("social-sidebar-browser");
             socialSidebar.setAttribute("src", "data:text/html;charset=utf-8,");
             socialSidebar.docShell.createAboutBlankContentViewer(null);
             socialSidebar.setAttribute("src", "about:blank");
 
             SelfSupportBackend.uninit();
+            CustomizationTabPreloader.uninit();
             SocialFlyout.unload();
             SocialShare.uninit();
           }
 
           // Destroy BackgroundPageThumbs resources.
           let {BackgroundPageThumbs} =
             Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", {});
           BackgroundPageThumbs._destroy();