Bug 629232 - Update remoteness of swapped browsers to ensure we can load them in a new window. r=mconley
authorJared Wein <jwein@mozilla.com>
Wed, 17 Oct 2018 14:42:48 +0000
changeset 497438 20a82622d638d03b4d4c63e7613129c4bbc54893
parent 497437 b062b1682bbd328a0327edf51f9040dcd9e1680a
child 497439 30a7005f36e433c8bfe8b0938e996a60f2618731
push id9996
push userarchaeopteryx@coole-files.de
push dateThu, 18 Oct 2018 18:37:15 +0000
treeherdermozilla-beta@8efe26839243 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs629232
milestone64.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 629232 - Update remoteness of swapped browsers to ensure we can load them in a new window. r=mconley We also need to guard against mStateFlags being undefined. Differential Revision: https://phabricator.services.mozilla.com/D7839
browser/base/content/tabbrowser.js
browser/components/sessionstore/test/browser.ini
browser/components/sessionstore/test/browser_movePendingTabToNewWindow.js
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -125,17 +125,17 @@ window._gBrowser = {
    * properties are accessed by consumers, `_insertBrowser` is called and
    * the browser is inserted to ensure that things don't break.  This list
    * provides the names of properties that may be called while the browser
    * is in its unbound (lazy) state.
    */
   _browserBindingProperties: [
     "canGoBack", "canGoForward", "goBack", "goForward", "permitUnload",
     "reload", "reloadWithFlags", "stop", "loadURI",
-    "gotoIndex", "currentURI", "documentURI",
+    "gotoIndex", "currentURI", "documentURI", "remoteType",
     "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",
@@ -1985,16 +1985,32 @@ window._gBrowser = {
               // Wait for load handler to be instantiated before
               // initializing the reload.
               aTab.addEventListener("SSTabRestoring", () => {
                 browser[name](params);
               }, { once: true });
               gBrowser._insertBrowser(aTab);
             };
           break;
+        case "remoteType":
+          getter = () => {
+            let url = SessionStore.getLazyTabValue(aTab, "url");
+            // Avoid recreating the same nsIURI object over and over again...
+            let uri;
+            if (browser._cachedCurrentURI) {
+              uri = browser._cachedCurrentURI;
+            } else {
+              uri = browser._cachedCurrentURI = Services.io.newURI(url);
+            }
+            return E10SUtils.getRemoteTypeForURI(url,
+                                                 gMultiProcessBrowser,
+                                                 undefined,
+                                                 uri);
+          };
+          break;
         case "userTypedValue":
         case "userTypedClear":
           getter = () => SessionStore.getLazyTabValue(aTab, name);
           break;
         default:
           getter = () => {
             if (AppConstants.NIGHTLY_BUILD) {
               let message =
@@ -3174,17 +3190,20 @@ window._gBrowser = {
       ourBrowser.setAttribute("usercontextid", otherBrowser.getAttribute("usercontextid"));
     }
 
     // That's gBrowser for the other window, not the tab's browser!
     var remoteBrowser = aOtherTab.ownerGlobal.gBrowser;
     var isPending = aOtherTab.hasAttribute("pending");
 
     let otherTabListener = remoteBrowser._tabListeners.get(aOtherTab);
-    let stateFlags = otherTabListener.mStateFlags;
+    let stateFlags = 0;
+    if (otherTabListener) {
+      stateFlags = otherTabListener.mStateFlags;
+    }
 
     // Expedite the removal of the icon if it was already scheduled.
     if (aOtherTab._soundPlayingAttrRemovalTimer) {
       clearTimeout(aOtherTab._soundPlayingAttrRemovalTimer);
       aOtherTab._soundPlayingAttrRemovalTimer = 0;
       aOtherTab.removeAttribute("soundplaying");
       remoteBrowser._tabAttrModified(aOtherTab, ["soundplaying"]);
     }
@@ -3289,17 +3308,17 @@ window._gBrowser = {
     if (closeWindow) {
       aOtherTab.ownerGlobal.close();
     } else {
       remoteBrowser._endRemoveTab(aOtherTab);
     }
 
     this.setTabTitle(aOurTab);
 
-    // If the tab was already selected (this happpens in the scenario
+    // If the tab was already selected (this happens in the scenario
     // of replaceTabWithWindow), notify onLocationChange, etc.
     if (aOurTab.selected)
       this.updateCurrentBrowser(true);
 
     if (modifiedAttrs.length) {
       this._tabAttrModified(aOurTab, modifiedAttrs);
     }
 
@@ -3570,50 +3589,57 @@ window._gBrowser = {
     if (this.tabs.length == tabs.length) {
       return null;
     }
 
     if (tabs.length == 1) {
       return this.replaceTabWithWindow(tabs[0], aOptions);
     }
 
-    // The order of the tabs is reserved.
-    // To avoid mutliple tab-switch, the active tab is "moved" lastly, if applicable.
+    // The order of the tabs is preserved.
+    // To avoid multiple tab-switch, the active tab is "moved" last, if applicable.
     // If applicable, the active tab remains active in the new window.
     let activeTab = gBrowser.selectedTab;
     let inactiveTabs = tabs.filter(t => t != activeTab);
     let activeTabNewIndex = tabs.indexOf(activeTab);
 
-
     // Play the closing animation for all selected tabs to give
     // immediate feedback while waiting for the new window to appear.
     if (this.animationsEnabled) {
       for (let tab of tabs) {
         tab.style.maxWidth = ""; // ensure that fade-out transition happens
         tab.removeAttribute("fadein");
       }
     }
 
     let win;
     let firstInactiveTab = inactiveTabs[0];
-    firstInactiveTab.linkedBrowser.addEventListener("EndSwapDocShells", function() {
+
+    let adoptRemainingTabs = () => {
       for (let i = 1; i < inactiveTabs.length; i++) {
         win.gBrowser.adoptTab(inactiveTabs[i], i);
       }
 
       if (activeTabNewIndex > -1) {
         win.gBrowser.adoptTab(activeTab, activeTabNewIndex, true /* aSelectTab */);
       }
 
       // Restore tab selection
       let winVisibleTabs = win.gBrowser.visibleTabs;
       let winTabLength = winVisibleTabs.length;
       win.gBrowser.addRangeToMultiSelectedTabs(winVisibleTabs[0],
                                                winVisibleTabs[winTabLength - 1]);
-    }, { once: true });
+    };
+
+    // Pending tabs don't get their docshell swapped, wait for their TabClose
+    if (firstInactiveTab.hasAttribute("pending")) {
+      firstInactiveTab.addEventListener("TabClose", adoptRemainingTabs, {once: true});
+    } else {
+      firstInactiveTab.linkedBrowser.addEventListener("EndSwapDocShells", adoptRemainingTabs, {once: true});
+    }
 
     win = this.replaceTabWithWindow(firstInactiveTab, aOptions);
     return win;
   },
 
   _updateTabsAfterInsert() {
     for (let i = 0; i < this.tabs.length; i++) {
       this.tabs[i]._tPos = i;
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -247,16 +247,17 @@ skip-if = (os == "mac") || (os == "linux
 
 [browser_906076_lazy_tabs.js]
 [browser_911547.js]
 [browser_1284886_suspend_tab.js]
 skip-if = !e10s
 [browser_async_window_flushing.js]
 [browser_focus_after_restore.js]
 [browser_forget_async_closings.js]
+[browser_movePendingTabToNewWindow.js]
 [browser_multiple_navigateAndRestore.js]
 run-if = e10s
 [browser_newtab_userTypedValue.js]
 skip-if = verify && debug
 [browser_parentProcessRestoreHash.js]
 run-if = e10s
 tags = openUILinkIn
 [browser_send_async_message_oom.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_movePendingTabToNewWindow.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests the behaviour of moving pending tabs to a new window. These
+ * pending tabs have yet to be restored and should be restored upon opening
+ * in the new window. This test covers moving a single pending tab at once
+ * as well as multiple tabs at the same time (using tab multiselection).
+ */
+add_task(async function test_movePendingTabToNewWindow() {
+  const TEST_URIS = [
+    "http://www.example.com/1",
+    "http://www.example.com/2",
+    "http://www.example.com/3",
+    "http://www.example.com/4",
+  ];
+
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      ["browser.sessionstore.restore_on_demand", true],
+      ["toolkit.cosmeticAnimations.enabled", false],
+    ],
+  });
+
+  let state = {
+    windows: [{
+      tabs: [
+        { entries: [{ url: TEST_URIS[0], triggeringPrincipal_base64 }] },
+        { entries: [{ url: TEST_URIS[1], triggeringPrincipal_base64 }] },
+        { entries: [{ url: TEST_URIS[2], triggeringPrincipal_base64 }] },
+        { entries: [{ url: TEST_URIS[3], triggeringPrincipal_base64 }] },
+      ],
+      selected: 4,
+    }],
+  };
+
+  await promiseBrowserState(state);
+
+  is(gBrowser.visibleTabs.length, 4, "Three tabs are visible to start the test");
+
+  let tabToSelect = gBrowser.visibleTabs[1];
+  ok(tabToSelect.hasAttribute("pending"), "Tab should be pending");
+
+  gBrowser.addRangeToMultiSelectedTabs(gBrowser.selectedTab, tabToSelect);
+  ok(!gBrowser.visibleTabs[0].multiselected, "First tab not multiselected");
+  ok(gBrowser.visibleTabs[1].multiselected, "Second tab multiselected");
+  ok(gBrowser.visibleTabs[2].multiselected, "Third tab multiselected");
+  ok(gBrowser.visibleTabs[3].multiselected, "Fourth tab multiselected");
+
+  let promiseNewWindow = BrowserTestUtils.waitForNewWindow();
+  gBrowser.replaceTabsWithWindow(tabToSelect);
+
+  info("Waiting for new window");
+  let newWindow = await promiseNewWindow;
+  isnot(newWindow, gBrowser.ownerGlobal, "Tab moved to new window");
+
+  let newWindowTabs = newWindow.gBrowser.visibleTabs;
+  await TestUtils.waitForCondition(() => {
+    return newWindowTabs.length == 3 &&
+           newWindowTabs[0].linkedBrowser.currentURI.spec == TEST_URIS[1] &&
+           newWindowTabs[1].linkedBrowser.currentURI.spec == TEST_URIS[2] &&
+           newWindowTabs[2].linkedBrowser.currentURI.spec == TEST_URIS[3];
+  }, "Wait for all three tabs to move to new window and load");
+
+  is(newWindowTabs.length, 3, "Three tabs should be in new window");
+  is(newWindowTabs[0].linkedBrowser.currentURI.spec, TEST_URIS[1], "Second tab moved");
+  is(newWindowTabs[1].linkedBrowser.currentURI.spec, TEST_URIS[2], "Third tab moved");
+  is(newWindowTabs[2].linkedBrowser.currentURI.spec, TEST_URIS[3], "Fourth tab moved");
+
+  ok(newWindowTabs[0].hasAttribute("pending"), "First tab in new window should still be pending");
+  ok(newWindowTabs[1].hasAttribute("pending"), "Second tab in new window should still be pending");
+  newWindow.gBrowser.clearMultiSelectedTabs(true);
+  ok(newWindowTabs.every(t => !t.multiselected), "No multiselection should be present");
+
+  promiseNewWindow = BrowserTestUtils.waitForNewWindow();
+  newWindow.gBrowser.replaceTabsWithWindow(newWindowTabs[0]);
+
+  info("Waiting for second new window");
+  let secondNewWindow = await promiseNewWindow;
+  await TestUtils.waitForCondition(() => secondNewWindow.gBrowser.selectedBrowser.currentURI.spec == TEST_URIS[1],
+    "Wait until the URI is updated");
+  is(secondNewWindow.gBrowser.visibleTabs.length, 1, "Only one tab in second new window");
+  is(secondNewWindow.gBrowser.selectedBrowser.currentURI.spec, TEST_URIS[1], "First tab moved");
+
+  await BrowserTestUtils.closeWindow(secondNewWindow);
+  await BrowserTestUtils.closeWindow(newWindow);
+});