Bug 1442694 - Preopen pinned tabs before session restore r=Gijs
☠☠ backed out by 4a87b6ac14c0 ☠ ☠
authorDoug Thayer <dothayer@mozilla.com>
Mon, 25 Feb 2019 19:19:07 +0000
changeset 518839 7a20c79a0e1236116417ef0b317e065f4e274b9f
parent 518838 7bffd747d54adf39be339cfac01d785e4654ea55
child 518840 f100f8631f78c50427ecd50f0dee4009d06ca4e1
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1442694
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1442694 - Preopen pinned tabs before session restore r=Gijs When we open firefox with pinned tabs, we first paint a window with one tab open, and then that tab gets displaced after the pinned tabs come in. This aims to ensure that our first paint contains the pinned tab, so that we don't have tabs moving around after first paint. MozReview-Commit-ID: GC1y6NlgLTd Differential Revision: https://phabricator.services.mozilla.com/D18742
browser/base/content/tabbrowser.css
browser/base/content/tabbrowser.js
browser/base/content/tabbrowser.xml
browser/components/sessionstore/SessionStore.jsm
browser/modules/BrowserWindowTracker.jsm
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -17,16 +17,21 @@
 .tab-throbber:not([busy]),
 .tab-icon-sound:not([soundplaying]):not([muted]):not([activemedia-blocked]),
 .tab-icon-sound[pinned],
 .tab-sharing-icon-overlay,
 .tab-icon-overlay {
   display: none;
 }
 
+.tab-icon-pending[preopened],
+.tab-icon-image[preopened] {
+  visibility: hidden;
+}
+
 .tab-sharing-icon-overlay[sharing]:not([selected]),
 .tab-icon-overlay[soundplaying][pinned],
 .tab-icon-overlay[muted][pinned],
 .tab-icon-overlay[activemedia-blocked][pinned],
 .tab-icon-overlay[crashed] {
   display: -moz-box;
 }
 
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -27,16 +27,17 @@ window._gBrowser = {
     Services.els.addSystemEventListener(document, "keydown", this, false);
     if (AppConstants.platform == "macosx") {
       Services.els.addSystemEventListener(document, "keypress", this, false);
     }
     window.addEventListener("sizemodechange", this);
     window.addEventListener("occlusionstatechange", this);
 
     this._setupInitialBrowserAndTab();
+    this._preopenPinnedTabs();
 
     if (Services.prefs.getBoolPref("browser.display.use_system_colors")) {
       this.tabpanels.style.backgroundColor = "-moz-default-background-color";
     } else if (Services.prefs.getIntPref("browser.display.document_color_use") == 2) {
       this.tabpanels.style.backgroundColor =
         Services.prefs.getCharPref("browser.display.background_color");
     }
 
@@ -378,16 +379,39 @@ window._gBrowser = {
     let filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
                   .createInstance(Ci.nsIWebProgress);
     filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
     this._tabListeners.set(tab, tabListener);
     this._tabFilters.set(tab, filter);
     browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
   },
 
+  _preopenPinnedTabs() {
+    let numPinnedTabs = 0;
+    let windows = browserWindows();
+    windows.getNext();
+    let isOnlyWindow = !windows.hasMoreElements();
+    if (isOnlyWindow) {
+      numPinnedTabs = Services.prefs.getIntPref("browser.tabs.firstWindowRestore.numPinnedTabs", 0);
+    }
+
+    for (let i = 0; i < numPinnedTabs; i++) {
+      let tab = this.addTrustedTab("about:blank", {
+        skipAnimation: true,
+        noInitialLabel: true,
+        skipBackgroundNotify: true,
+        createLazyBrowser: true,
+        pinned: true,
+        isForFirstWindowRestore: true,
+      });
+
+      tab.setAttribute("preopened", "true");
+    }
+  },
+
   /**
    * BEGIN FORWARDED BROWSER PROPERTIES.  IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
    * MAKE SURE TO ADD IT HERE AS WELL.
    */
   get canGoBack() {
     return this.selectedBrowser.canGoBack;
   },
 
@@ -588,46 +612,66 @@ window._gBrowser = {
   },
 
   _updateTabBarForPinnedTabs() {
     this.tabContainer._unlockTabSizing();
     this.tabContainer._positionPinnedTabs();
     this.tabContainer._updateCloseButtons();
   },
 
-  _notifyPinnedStatus(aTab) {
+  _sendPinnedTabContentMessage(aTab) {
     this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: aTab.pinned });
+  },
+
+  _notifyPinnedStatus(aTab, aDeferContentMessage = false) {
+    if (!aDeferContentMessage) {
+      this._sendPinnedTabContentMessage(aTab);
+    }
 
     let event = document.createEvent("Events");
     event.initEvent(aTab.pinned ? "TabPinned" : "TabUnpinned", true, false);
     aTab.dispatchEvent(event);
   },
 
+  _maybeUpdateNumPinnedTabsPref() {
+    if (BrowserWindowTracker.getTopWindow() == window) {
+      Services.prefs.setIntPref("browser.tabs.firstWindowRestore.numPinnedTabs",
+                                this._numPinnedTabs);
+    }
+  },
+
+  activatePreopenedPinnedTab(aTab) {
+    this._insertBrowser(aTab);
+    this._sendPinnedTabContentMessage(aTab);
+  },
+
   pinTab(aTab) {
     if (aTab.pinned)
       return;
 
     if (aTab.hidden)
       this.showTab(aTab);
 
     this.moveTabTo(aTab, this._numPinnedTabs);
     aTab.setAttribute("pinned", "true");
     this._updateTabBarForPinnedTabs();
     this._notifyPinnedStatus(aTab);
+    this._maybeUpdateNumPinnedTabsPref();
   },
 
   unpinTab(aTab) {
     if (!aTab.pinned)
       return;
 
     this.moveTabTo(aTab, this._numPinnedTabs - 1);
     aTab.removeAttribute("pinned");
     aTab.style.marginInlineStart = "";
     this._updateTabBarForPinnedTabs();
     this._notifyPinnedStatus(aTab);
+    this._maybeUpdateNumPinnedTabsPref();
   },
 
   previewTab(aTab, aCallback) {
     let currentTab = this.selectedTab;
     try {
       // Suppress focus, ownership and selected tab changes
       this._previewMode = true;
       this.selectedTab = aTab;
@@ -2291,16 +2335,17 @@ window._gBrowser = {
     bulkOrderedOpen,
     charset,
     createLazyBrowser,
     eventDetail,
     focusUrlBar,
     forceNotRemote,
     fromExternal,
     index,
+    isForFirstWindowRestore,
     lazyTabTitle,
     name,
     nextTabParentId,
     noInitialLabel,
     noReferrer,
     opener,
     openerBrowser,
     originPrincipal,
@@ -2474,16 +2519,19 @@ window._gBrowser = {
       if (tabAfter) {
         this._updateTabsAfterInsert();
       } else {
         t._tPos = index;
       }
 
       if (pinned) {
         this._updateTabBarForPinnedTabs();
+        if (!isForFirstWindowRestore) {
+          this._maybeUpdateNumPinnedTabsPref();
+        }
       }
       this.tabContainer._setPositionalAttributes();
 
       TabBarVisibility.update();
 
       // If we don't have a preferred remote type, and we have a remote
       // opener, use the opener's remote type.
       if (!preferredRemoteType && openerBrowser) {
@@ -2553,23 +2601,25 @@ window._gBrowser = {
 
         if (lazyBrowserURI) {
           // Lazy browser must be explicitly registered so tab will appear as
           // a switch-to-tab candidate in autocomplete.
           this.UrlbarProviderOpenTabs.registerOpenTab(lazyBrowserURI.spec,
                                                       userContextId);
           b.registeredOpenURI = lazyBrowserURI;
         }
-        SessionStore.setTabState(t, {
-          entries: [{
-            url: lazyBrowserURI ? lazyBrowserURI.spec : "about:blank",
-            title: lazyTabTitle,
-            triggeringPrincipal_base64: E10SUtils.serializePrincipal(triggeringPrincipal),
-          }],
-        });
+        if (!isForFirstWindowRestore) {
+          SessionStore.setTabState(t, {
+            entries: [{
+              url: lazyBrowserURI ? lazyBrowserURI.spec : "about:blank",
+              title: lazyTabTitle,
+              triggeringPrincipal_base64: E10SUtils.serializePrincipal(triggeringPrincipal),
+            }],
+          });
+        }
       } else {
         this._insertBrowser(t, true);
       }
     } catch (e) {
       Cu.reportError("Failed to create tab");
       Cu.reportError(e);
       t.remove();
       if (t.linkedBrowser) {
@@ -2597,17 +2647,19 @@ window._gBrowser = {
       if (!aURIObject ||
           (doGetProtocolFlags(aURIObject) & URI_INHERITS_SECURITY_CONTEXT)) {
         b.createAboutBlankContentViewer(originPrincipal);
       }
     }
 
     // If we didn't swap docShells with a preloaded browser
     // then let's just continue loading the page normally.
-    if (!usingPreloadedContent && (!uriIsAboutBlank || !allowInheritPrincipal)) {
+    if (!usingPreloadedContent &&
+        !createLazyBrowser &&
+        (!uriIsAboutBlank || !allowInheritPrincipal)) {
       // pretend the user typed this so it'll be available till
       // the document successfully loads
       if (aURI && !gInitialPages.includes(aURI)) {
         b.userTypedValue = aURI;
       }
 
       let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
       if (allowThirdPartyFixup) {
@@ -2650,17 +2702,17 @@ window._gBrowser = {
       requestAnimationFrame(function() {
         // kick the animation off
         t.setAttribute("fadein", "true");
       });
     }
 
     // Additionally send pinned tab events
     if (pinned) {
-      this._notifyPinnedStatus(t);
+      this._notifyPinnedStatus(t, isForFirstWindowRestore);
     }
 
     return t;
   },
 
   moveTabsToStart(contextTab) {
     let tabs = contextTab.multiselected ?
       this.selectedTabs :
@@ -3148,18 +3200,20 @@ window._gBrowser = {
     if (aTab.hidden)
       this.tabContainer._updateHiddenTabsStatus();
 
     // ... and fix up the _tPos properties immediately.
     for (let i = aTab._tPos; i < this.tabs.length; i++)
       this.tabs[i]._tPos = i;
 
     if (!this._windowIsClosing) {
-      if (wasPinned)
+      if (wasPinned) {
         this.tabContainer._positionPinnedTabs();
+        this._maybeUpdateNumPinnedTabsPref();
+      }
 
       // update tab close buttons state
       this.tabContainer._updateCloseButtons();
 
       setTimeout(function(tabs) {
         tabs._lastTabClosedByMouse = false;
       }, 0, this.tabContainer);
     }
@@ -3834,17 +3888,16 @@ window._gBrowser = {
     let createLazyBrowser = !aTab.linkedPanel;
     let params = {
       eventDetail: { adoptedTab: aTab },
       preferredRemoteType: linkedBrowser.remoteType,
       sameProcessAsFrameLoader: linkedBrowser.frameLoader,
       skipAnimation: true,
       index: aIndex,
       createLazyBrowser,
-      allowInheritPrincipal: createLazyBrowser,
     };
 
     let numPinned = this._numPinnedTabs;
     if (aIndex < numPinned || (aTab.pinned && aIndex == numPinned)) {
       params.pinned = true;
     }
 
     if (aTab.hasAttribute("usercontextid")) {
@@ -5011,16 +5064,17 @@ class TabProgressListener {
           // remoteness or is a new tab/window).
           this.mBrowser.urlbarChangeTracker.startedLoad();
         }
         delete this.mBrowser.initialPageLoadedFromUserAction;
         // If the browser is loading it must not be crashed anymore
         this.mTab.removeAttribute("crashed");
       }
 
+      this.mTab.removeAttribute("preopened");
       if (this._shouldShowProgress(aRequest)) {
         if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) &&
             aWebProgress && aWebProgress.isTopLevel) {
           this.mTab.setAttribute("busy", "true");
           gBrowser._tabAttrModified(this.mTab, ["busy"]);
           this.mTab._notselectedsinceload = !this.mTab.selected;
           gBrowser.syncThrobberAnimations(this.mTab);
         }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1932,20 +1932,20 @@
                   anonid="tab-loading-burst"
                   class="tab-loading-burst"/>
         <xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
                   class="tab-content" align="center">
           <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
                     anonid="tab-throbber"
                     class="tab-throbber"
                     layer="true"/>
-          <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected,pendingicon"
+          <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected,pendingicon,preopened"
                     anonid="tab-icon-pending"
                     class="tab-icon-pending"/>
-          <xul:image xbl:inherits="src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
+          <xul:image xbl:inherits="src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing,preopened"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
                      validate="never"
                      role="presentation"/>
           <xul:image xbl:inherits="sharing,selected=visuallyselected,pinned"
                      anonid="sharing-icon"
                      class="tab-sharing-icon-overlay"
                      role="presentation"/>
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -726,16 +726,17 @@ var SessionStoreInternal = {
 
     this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
     this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
 
     this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
     this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
 
     this._restore_on_demand = this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
+    this._restore_pinned_tabs_on_demand = this._prefBranch.getBoolPref("sessionstore.restore_pinned_tabs_on_demand");
     this._prefBranch.addObserver("sessionstore.restore_on_demand", this, true);
 
     gResistFingerprintingEnabled = Services.prefs.getBoolPref("privacy.resistFingerprinting");
     Services.prefs.addObserver("privacy.resistFingerprinting", this);
   },
 
   /**
    * Called on application shutdown, after notifications:
@@ -3198,16 +3199,17 @@ var SessionStoreInternal = {
     } catch (e) {}
 
     // Start the throbber to pretend we're doing something while actually
     // waiting for data from the frame script. This throbber is disabled
     // if the URI is a local about: URI.
     if (!uriObj || (uriObj && !window.gBrowser.isLocalAboutURI(uriObj))) {
       tab.setAttribute("busy", "true");
     }
+    tab.removeAttribute("preopened");
 
     // Hack to ensure that the about:home, about:newtab, and about:welcome
     // favicon is loaded instantaneously, to avoid flickering and improve
     // perceived performance.
     window.gBrowser.setDefaultIcon(tab, uriObj);
 
     TAB_STATE_FOR_BROWSER.set(tab.linkedBrowser, TAB_STATE_WILL_RESTORE);
 
@@ -3645,26 +3647,29 @@ var SessionStoreInternal = {
 
     // disable smooth scrolling while adding, moving, removing and selecting tabs
     let arrowScrollbox = tabbrowser.tabContainer.arrowScrollbox;
     let smoothScroll = arrowScrollbox.smoothScroll;
     arrowScrollbox.smoothScroll = false;
 
     // We need to keep track of the initially open tabs so that they
     // can be moved to the end of the restored tabs.
-    let initialTabs;
+    let initialTabs = [];
     if (!overwriteTabs && firstWindow) {
       initialTabs = Array.slice(tabbrowser.tabs);
     }
 
     // Get rid of tabs that aren't needed anymore.
     if (overwriteTabs) {
       for (let i = tabbrowser.browsers.length - 1; i >= 0; i--) {
-        if (!tabbrowser.tabs[i].selected) {
+        if (!tabbrowser.tabs[i].selected &&
+            !tabbrowser.tabs[i].hasAttribute("preopened")) {
           tabbrowser.removeTab(tabbrowser.tabs[i]);
+        } else if (tabbrowser.tabs[i].hasAttribute("preopened")) {
+          initialTabs.push(tabbrowser.tabs[i]);
         }
       }
     }
 
     let restoreTabsLazily = this._prefBranch.getBoolPref("sessionstore.restore_tabs_lazily") && this._restore_on_demand;
 
     for (var t = 0; t < newTabCount; t++) {
       let tabData = winData.tabs[t];
@@ -3673,23 +3678,34 @@ var SessionStoreInternal = {
       let select = t == selectTab - 1;
       let tab;
 
       // Re-use existing selected tab if possible to avoid the overhead of
       // selecting a new tab.
       if (select &&
           tabbrowser.selectedTab.userContextId == userContextId) {
         tab = tabbrowser.selectedTab;
-        if (!tabData.pinned) {
+        if (tab.pinned && !tabData.pinned) {
           tabbrowser.unpinTab(tab);
+        } else if (!tab.pinned && tabData.pinned) {
+          tabbrowser.removeTab(tabbrowser.tabs[t]);
+          tabbrowser.pinTab(tab);
+          tabbrowser.moveTabTo(tab, t);
         }
+
         tabbrowser.moveTabToEnd();
         if (aWindow.gMultiProcessBrowser && !tab.linkedBrowser.isRemoteBrowser) {
           tabbrowser.updateBrowserRemoteness(tab.linkedBrowser, true);
         }
+      } else if (tabData.pinned &&
+          tabbrowser.tabs[t] &&
+          tabbrowser.tabs[t].pinned &&
+          !tabbrowser.tabs[t].linkedPanel) {
+        tab = tabbrowser.tabs[t];
+        tabbrowser.activatePreopenedPinnedTab(tab);
       }
 
       // Add a new tab if needed.
       if (!tab) {
         let createLazyBrowser = restoreTabsLazily && !select && !tabData.pinned;
 
         let url = "about:blank";
         if (createLazyBrowser && tabData.entries && tabData.entries.length) {
@@ -3728,21 +3744,24 @@ var SessionStoreInternal = {
       }
 
       if (tabData.pinned) {
         tabbrowser.pinTab(tab);
       }
     }
 
     // Move the originally open tabs to the end.
-    if (initialTabs) {
-      let endPosition = tabbrowser.tabs.length - 1;
-      for (let i = 0; i < initialTabs.length; i++) {
-        tabbrowser.unpinTab(initialTabs[i]);
-        tabbrowser.moveTabTo(initialTabs[i], endPosition);
+    let endPosition = tabbrowser.tabs.length - 1;
+    for (let tab of initialTabs) {
+      if (tab.hasAttribute("preopened") &&
+          !tab.linkedPanel) {
+        tabbrowser.removeTab(tab);
+      } else if (!tab.hasAttribute("preopened")) {
+        tabbrowser.unpinTab(tab);
+        tabbrowser.moveTabTo(tab, endPosition);
       }
     }
 
     // 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)
@@ -3993,16 +4012,19 @@ var SessionStoreInternal = {
     if (selectedIndex > -1) {
       this.restoreTab(tabbrowser.selectedTab, aTabData[selectedIndex]);
     }
 
     // Restore all tabs.
     for (let t = 0; t < aTabs.length; t++) {
       if (t != selectedIndex) {
         this.restoreTab(aTabs[t], aTabData[t]);
+        if (this._restore_pinned_tabs_on_demand) {
+          aTabs[t].removeAttribute("preopened");
+        }
       }
     }
   },
 
   // Restores the given tab state for a given tab.
   restoreTab(tab, tabData, options = {}) {
     let browser = tab.linkedBrowser;
 
--- a/browser/modules/BrowserWindowTracker.jsm
+++ b/browser/modules/BrowserWindowTracker.jsm
@@ -91,16 +91,21 @@ function _trackWindowOrder(window) {
 }
 
 function _untrackWindowOrder(window) {
   let idx = _trackedWindows.indexOf(window);
   if (idx >= 0)
     _trackedWindows.splice(idx, 1);
 }
 
+function _trackPinnedTabs(window) {
+  Services.prefs.setIntPref("browser.tabs.firstWindowRestore.numPinnedTabs",
+                            window.gBrowser._numPinnedTabs);
+}
+
 // Methods that impact a window. Put into single object for organization.
 var WindowHelper = {
   addWindow(window) {
     // Add event listeners
     TAB_EVENTS.forEach(function(event) {
       window.gBrowser.tabContainer.addEventListener(event, _handleEvent);
     });
     WINDOW_EVENTS.forEach(function(event) {
@@ -133,16 +138,17 @@ var WindowHelper = {
 
   onActivate(window) {
     // If this window was the last focused window, we don't need to do anything
     if (window == _trackedWindows[0])
       return;
 
     _untrackWindowOrder(window);
     _trackWindowOrder(window);
+    _trackPinnedTabs(window);
 
     _updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser);
   },
 };
 
 this.BrowserWindowTracker = {
   /**
    * Get the most recent browser window.