Bug 1284886 - Add discardBrowser method to tabbrowser.xml, which returns tab to its lazy state. r=dao, r=mikedeboer
☠☠ backed out by 72d94b046a5a ☠ ☠
authorKevin Jones <kevinhowjones@gmail.com>
Sun, 03 Sep 2017 15:31:00 -0400
changeset 428348 14e59ce2ce01091411db34aa054f9f4a78f75430
parent 428347 c77149fc1187449a18cdd1b6e0ef129775c45b48
child 428349 55e08f65ac23b380ca995319032f07121d46bf71
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao, mikedeboer
bugs1284886
milestone57.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 1284886 - Add discardBrowser method to tabbrowser.xml, which returns tab to its lazy state. r=dao, r=mikedeboer
browser/base/content/tabbrowser.xml
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/test/browser.ini
browser/components/sessionstore/test/browser_1284886_suspend_tab.html
browser/components/sessionstore/test/browser_1284886_suspend_tab.js
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2266,17 +2266,18 @@
         "goHome", "homePage", "gotoIndex", "currentURI", "documentURI",
         "preferences", "imageDocument", "isRemoteBrowser", "messageManager",
         "getTabBrowser", "finder", "fastFind", "sessionHistory", "contentTitle",
         "characterSet", "fullZoom", "textZoom", "webProgress",
         "addProgressListener", "removeProgressListener", "audioPlaybackStarted",
         "audioPlaybackStopped", "pauseMedia", "stopMedia",
         "resumeMedia", "mute", "unmute", "blockedPopups", "lastURI",
         "purgeSessionHistory", "stopScroll", "startScroll",
-        "userTypedValue", "userTypedClear", "mediaBlocked"
+        "userTypedValue", "userTypedClear", "mediaBlocked",
+        "didStartLoadSinceLastUserTyping"
       ]</field>
 
       <method name="_createLazyBrowser">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             let browser = aTab.linkedBrowser;
 
@@ -2294,16 +2295,19 @@
                   getter = () => SessionStore.getLazyTabValue(aTab, "title");
                   break;
                 case "currentURI":
                   getter = () => {
                     let url = SessionStore.getLazyTabValue(aTab, "url");
                     return Services.io.newURI(url);
                   };
                   break;
+                case "didStartLoadSinceLastUserTyping":
+                  getter = () => () => false;
+                  break;
                 case "fullZoom":
                 case "textZoom":
                   getter = () => 1;
                   break;
                 case "getTabBrowser":
                   getter = () => () => this;
                   break;
                 case "isRemoteBrowser":
@@ -2436,16 +2440,66 @@
 
             var evt = new CustomEvent("TabBrowserInserted",
               { bubbles: true, detail: { insertedOnTabCreation: aInsertedOnTabCreation } });
             aTab.dispatchEvent(evt);
           ]]>
         </body>
       </method>
 
+      <method name="discardBrowser">
+        <parameter name="aBrowser"/>
+        <body>
+          <![CDATA[
+            "use strict";
+
+            let tab = this.getTabForBrowser(aBrowser);
+
+            if (!tab ||
+                tab.selected ||
+                tab.closing ||
+                this._windowIsClosing ||
+                !aBrowser.isConnected ||
+                !aBrowser.isRemoteBrowser ||
+                aBrowser.frameLoader.tabParent.hasBeforeUnload) {
+              return;
+            }
+
+            // Set browser parameters for when browser is restored.  Also remove
+            // listeners and set up lazy restore data in SessionStore. This must
+            // be done before aBrowser is destroyed and removed from the document.
+            tab._browserParams = { uriIsAboutBlank: aBrowser.currentURI.spec == "about:blank",
+                                   remoteType: aBrowser.remoteType,
+                                   usingPreloadedContent: false };
+
+            SessionStore.resetBrowserToLazyState(tab);
+
+            this._outerWindowIDBrowserMap.delete(aBrowser.outerWindowID);
+
+            // Remove the tab's filter and progress listener.
+            let filter = this._tabFilters.get(tab);
+            let listener = this._tabListeners.get(tab);
+            aBrowser.webProgress.removeProgressListener(filter);
+            filter.removeProgressListener(listener);
+            listener.destroy();
+
+            this._tabListeners.delete(tab);
+            this._tabFilters.delete(tab);
+
+            aBrowser.destroy();
+
+            let notificationbox = this.getNotificationBox(aBrowser);
+            this.mPanelContainer.removeChild(notificationbox);
+            tab.removeAttribute("linkedpanel");
+
+            this._createLazyBrowser(tab);
+          ]]>
+        </body>
+      </method>
+
       <method name="addTab">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <parameter name="aOwner"/>
         <parameter name="aAllowThirdPartyFixup"/>
         <body>
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -365,16 +365,20 @@ this.SessionStore = {
   getSessionHistory(tab, updatedCallback) {
     return SessionStoreInternal.getSessionHistory(tab, updatedCallback);
   },
 
   undoCloseById(aClosedId) {
     return SessionStoreInternal.undoCloseById(aClosedId);
   },
 
+  resetBrowserToLazyState(tab) {
+    return SessionStoreInternal.resetBrowserToLazyState(tab);
+  },
+
   /**
    * Determines whether the passed version number is compatible with
    * the current version number of the SessionStore.
    *
    * @param version The format and version of the file, as an array, e.g.
    * ["sessionrestore", 1]
    */
   isFormatVersionCompatible(version) {
@@ -1893,29 +1897,17 @@ var SessionStoreInternal = {
    * @param aWindow
    *        Window reference
    * @param aTab
    *        Tab reference
    * @param aNoNotification
    *        bool Do not save state if we're updating an existing tab
    */
   onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
-    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;
-    if (previousState) {
-      this._resetTabRestoringState(aTab);
-      if (previousState == TAB_STATE_RESTORING)
-        this.restoreNextTab();
-    }
+    this.cleanUpRemovedBrowser(aTab);
 
     if (!aNoNotification) {
       this.saveStateDelayed(aWindow);
     }
   },
 
   /**
    * When a tab closes, collect its properties
@@ -1975,16 +1967,67 @@ var SessionStoreInternal = {
     }
 
     // Remember the closed tab to properly handle any last updates included in
     // the final "update" message sent by the frame script's unload handler.
     this._closedTabs.set(permanentKey, {closedTabs, tabData});
   },
 
   /**
+   * Remove listeners which were added when browser was inserted and reset restoring state.
+   * Also re-instate lazy data and basically revert tab to its lazy browser state.
+   * @param aTab
+   *        Tab reference
+   */
+  resetBrowserToLazyState(aTab) {
+    let browser = aTab.linkedBrowser;
+    // Browser is already lazy so don't do anything.
+    if (!browser.isConnected) {
+      return;
+    }
+
+    this.cleanUpRemovedBrowser(aTab);
+
+    aTab.setAttribute("pending", "true");
+
+    this._lastKnownFrameLoader.delete(browser.permanentKey);
+    this._crashedBrowsers.delete(browser.permanentKey);
+    aTab.removeAttribute("crashed");
+
+    aTab.__SS_lazyData = {
+      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;
+    if (previousState) {
+      this._resetTabRestoringState(aTab);
+      if (previousState == TAB_STATE_RESTORING)
+        this.restoreNextTab();
+    }
+  },
+
+  /**
    * Insert a given |tabData| object into the list of |closedTabs|. We will
    * determine the right insertion point based on the .closedAt properties of
    * all tabs already in the list. The list will be truncated to contain a
    * maximum of |this._max_tabs_undo| entries.
    *
    * @param closedTabs (array)
    *        The list of closed tabs for a window.
    * @param tabData (object)
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -50,16 +50,17 @@ support-files =
   browser_739531_frame.html
   browser_911547_sample.html
   browser_911547_sample.html^headers^
   restore_redirect_http.html
   restore_redirect_http.html^headers^
   restore_redirect_js.html
   restore_redirect_target.html
   browser_1234021_page.html
+  browser_1284886_suspend_tab.html
 
 #NB: the following are disabled
 #  browser_464620_a.html
 #  browser_464620_b.html
 #  browser_464620_xd.html
 
 
 #disabled-for-intermittent-failures--bug-766044, browser_459906_empty.html
@@ -223,16 +224,17 @@ skip-if = true
 skip-if = true
 
 # Disabled on OS X:
 [browser_625016.js]
 skip-if = os == "mac" || (os == "linux" && debug) # linux, Bug 1348583
 
 [browser_906076_lazy_tabs.js]
 [browser_911547.js]
+[browser_1284886_suspend_tab.js]
 [browser_send_async_message_oom.js]
 [browser_multiple_navigateAndRestore.js]
 run-if = e10s
 [browser_async_window_flushing.js]
 [browser_forget_async_closings.js]
 [browser_newtab_userTypedValue.js]
 [browser_parentProcessRestoreHash.js]
 run-if = e10s
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_1284886_suspend_tab.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+  <script>
+    window.onbeforeunload = function() {
+      return true;
+    }
+  </script>
+</head>
+<body>
+TEST PAGE
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_1284886_suspend_tab.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function test() {
+  let url = "about:robots";
+  let tab0 = gBrowser.tabs[0];
+  let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  gBrowser.discardBrowser(tab0.linkedBrowser);
+  ok(!tab0.linkedPanel, "tab0 is suspended");
+
+  await BrowserTestUtils.switchTab(gBrowser, tab0);
+
+  // Test that active tab is not able to be suspended.
+  gBrowser.discardBrowser(tab0.linkedBrowser);
+  ok(tab0.linkedPanel, "active tab is not able to be suspended");
+
+  // Test that tab that is closing is not able to be suspended.
+  gBrowser._beginRemoveTab(tab1);
+  gBrowser.discardBrowser(tab1.linkedBrowser);
+
+  ok(tab1.linkedPanel, "cannot suspend a tab that is closing");
+
+  gBrowser._endRemoveTab(tab1);
+
+  // Test that tab with beforeunload handler is not able to be suspended.
+  url = "http://example.com/browser/browser/components/sessionstore/test/browser_1284886_suspend_tab.html";
+
+  tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+  await BrowserTestUtils.switchTab(gBrowser, tab0);
+
+  gBrowser.discardBrowser(tab1.linkedBrowser);
+  ok(tab1.linkedPanel, "cannot suspend a tab with beforeunload handler");
+
+  await promiseRemoveTab(tab1);
+
+  // Test that remote tab is not able to be suspended.
+  url = "about:robots";
+  tab1 = BrowserTestUtils.addTab(gBrowser, url, { forceNotRemote: true });
+  await promiseBrowserLoaded(tab1.linkedBrowser, true, url);
+  await BrowserTestUtils.switchTab(gBrowser, tab1);
+  await BrowserTestUtils.switchTab(gBrowser, tab0);
+
+  gBrowser.discardBrowser(tab1.linkedBrowser);
+  ok(tab1.linkedPanel, "cannot suspend a remote tab");
+
+  promiseRemoveTab(tab1);
+});
+