Bug 1345090 - Modify SessionStore to restore tabs with lazy browsers. r=mikedeboer,dao

MozReview-Commit-ID: 5J5UqlWMxKX
This commit is contained in:
Kevin Jones 2017-04-20 11:01:09 +02:00
parent 34ae9eb687
commit a7fa6eb9b7
14 changed files with 245 additions and 77 deletions

View File

@ -1613,6 +1613,9 @@ pref("browser.formautofill.experimental", false);
pref("browser.formautofill.enabled", false);
pref("browser.formautofill.loglevel", "Warn");
// Whether or not to restore a session with lazy-browser tabs.
pref("browser.sessionstore.restore_tabs_lazily", true);
// Enable safebrowsing v4 tables (suffixed by "-proto") update.
#ifdef NIGHTLY_BUILD
pref("urlclassifier.malwareTable", "goog-malware-shavar,goog-unwanted-shavar,goog-malware-proto,goog-unwanted-proto,test-malware-simple,test-unwanted-simple");

View File

@ -2034,7 +2034,8 @@
"addProgressListener", "removeProgressListener", "audioPlaybackStarted",
"audioPlaybackStopped", "adjustPriority", "pauseMedia", "stopMedia",
"blockMedia", "resumeMedia", "mute", "unmute", "blockedPopups", "lastURI",
"purgeSessionHistory", "stopScroll", "startScroll"
"purgeSessionHistory", "stopScroll", "startScroll",
"userTypedValue", "userTypedClear"
]</field>
<method name="_createLazyBrowser">
@ -2052,11 +2053,51 @@
switch (name) {
case "permitUnload":
getter = () => {
return function() {
return () => {
return { permitUnload: true, timedOut: false };
};
};
break;
case "reload":
case "reloadWithFlags":
getter = () => {
return (params) => {
// Wait for load handler to be instantiated before
// initializing the reload.
aTab.addEventListener("SSTabRestoring", () => {
browser[name](params);
}, { once: true });
gBrowser._insertBrowser(aTab);
};
};
break;
case "isRemoteBrowser":
getter = () => {
return browser.getAttribute("remote") == "true";
};
break;
case "audioMuted":
getter = () => {
return false;
};
break;
case "currentURI":
getter = () => {
let url = SessionStore.getLazyTabValue(aTab, "url");
return Services.io.newURI(url);
};
break;
case "contentTitle":
getter = () => {
return SessionStore.getLazyTabValue(aTab, "title");
};
break;
case "userTypedValue":
case "userTypedClear":
getter = () => {
return SessionStore.getLazyTabValue(aTab, name);
};
break;
default:
getter = () => {
this._insertBrowser(aTab);
@ -2084,9 +2125,8 @@
<![CDATA[
"use strict";
// If browser is already inserted, or aTab doesn't have a
// browser, don't do anything.
if (aTab.linkedPanel || !aTab.linkedBrowser) {
// If browser is already inserted don't do anything.
if (aTab.linkedPanel) {
return;
}
@ -4876,7 +4916,8 @@
tab.linkedBrowser &&
tab.linkedBrowser.isRemoteBrowser) {
label += " - e10s";
if (Services.appinfo.maxWebProcessCount > 1) {
if (tab.linkedBrowser.frameLoader &&
Services.appinfo.maxWebProcessCount > 1) {
label += " (" + tab.linkedBrowser.frameLoader.tabParent.osPid + ")";
}
}
@ -7245,6 +7286,11 @@
<parameter name="aMuteReason"/>
<body>
<![CDATA[
// Do not attempt to toggle mute state if browser is lazy.
if (!this.linkedPanel) {
return;
}
let tabContainer = this.parentNode;
let browser = this.linkedBrowser;
let modifiedAttrs = [];

View File

@ -334,6 +334,10 @@ this.SessionStore = {
SessionStoreInternal.deleteTabValue(aTab, aKey);
},
getLazyTabValue(aTab, aKey) {
return SessionStoreInternal.getLazyTabValue(aTab, aKey);
},
getGlobalValue: function ss_getGlobalValue(aKey) {
return SessionStoreInternal.getGlobalValue(aKey);
},
@ -1871,6 +1875,15 @@ var SessionStoreInternal = {
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);
this.restoreTab(aTab, tabState);
}
// The browser has been inserted now, so lazy data is no longer relevant.
delete aTab.__SS_lazyData;
},
/**
@ -2545,6 +2558,19 @@ var SessionStoreInternal = {
}
},
/**
* 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];
},
getGlobalValue: function ssi_getGlobalValue(aKey) {
return this._globalState.get(aKey);
},
@ -3272,6 +3298,9 @@ var SessionStoreInternal = {
let numVisibleTabs = 0;
let createLazyBrowser = this._prefBranch.getBoolPref("sessionstore.restore_tabs_lazily") &&
this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
for (var t = 0; t < newTabCount; t++) {
// When trying to restore into existing tab, we also take the userContextId
// into account if present.
@ -3280,7 +3309,8 @@ var SessionStoreInternal = {
(tabbrowser.tabs[t].getAttribute("usercontextid") == (userContextId || ""));
let tab = reuseExisting ? this._maybeUpdateBrowserRemoteness(tabbrowser.tabs[t])
: tabbrowser.addTab("about:blank",
{ skipAnimation: true,
{ createLazyBrowser,
skipAnimation: true,
userContextId,
skipBackgroundNotify: true });
@ -3595,9 +3625,7 @@ var SessionStoreInternal = {
tabbrowser.selectedBrowser == browser ||
loadArguments;
if (!willRestoreImmediately && !forceOnDemand) {
TabRestoreQueue.add(tab);
}
let isBrowserInserted = browser.isConnected;
// Increase the busy state counter before modifying the tab.
this._setWindowStateBusy(window);
@ -3665,14 +3693,6 @@ var SessionStoreInternal = {
// Save the index in case we updated it above.
tabData.index = activeIndex + 1;
// 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);
// keep the data around to prevent dataloss in case
// a tab gets closed before it's been properly restored
browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
browser.setAttribute("pending", "true");
tab.setAttribute("pending", "true");
@ -3700,26 +3720,57 @@ var SessionStoreInternal = {
userTypedClear: tabData.userTypedClear || 0
});
this._sendRestoreHistory(browser, {tabData, epoch, loadArguments});
// Update tab label and icon to show something
// while we wait for the messages to be processed.
this.updateTabLabelAndIcon(tab, tabData);
// Restore tab attributes.
if ("attributes" in tabData) {
TabAttributes.set(tab, tabData.attributes);
}
// 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, loadArguments, reloadInFreshProcess,
restoreContentReason);
} else if (!forceOnDemand) {
this.restoreNextTab();
if (isBrowserInserted) {
// 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;
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, loadArguments, reloadInFreshProcess,
restoreContentReason);
} else if (!forceOnDemand) {
TabRestoreQueue.add(tab);
this.restoreNextTab();
}
} else {
// __SS_lazyData 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 = {
url,
title,
userTypedValue: tabData.userTypedValue || "",
userTypedClear: tabData.userTypedClear || 0
};
}
// Update tab label and icon to show something
// while we wait for the messages to be processed.
this.updateTabLabelAndIcon(tab, tabData);
// Decrease the busy state counter after we're done.
this._setWindowStateReady(window);
},

View File

@ -32,11 +32,27 @@ function test() {
"We still know that no load is ongoing");
is(gURLBar.value, "example.com",
"Address bar's value correctly restored");
// Change tabs to make sure address bar value gets updated
gBrowser.selectedTab = gBrowser.tabContainer.getItemAtIndex(0);
is(gURLBar.value, "about:mozilla",
"Address bar's value correctly updated");
runNextTest();
// Change tabs to make sure address bar value gets updated. If tab is
// lazy, wait for SSTabRestored to ensure address bar has time to update.
let tabToSelect = gBrowser.tabContainer.getItemAtIndex(0);
if (tabToSelect.linkedBrowser.isConnected) {
gBrowser.selectedTab = tabToSelect;
is(gURLBar.value, "about:mozilla",
"Address bar's value correctly updated");
runNextTest();
} else {
gBrowser.tabContainer.addEventListener("SSTabRestored",
function SSTabRestored(event) {
if (event.target == tabToSelect) {
gBrowser.tabContainer.removeEventListener("SSTabRestored", SSTabRestored, true);
is(gURLBar.value, "about:mozilla",
"Address bar's value correctly updated");
runNextTest();
}
}, true);
gBrowser.selectedTab = tabToSelect;
}
});
}
@ -60,15 +76,34 @@ function test() {
"No history entries still sets currentURI to about:blank");
is(browser.userTypedValue, "example.org",
"userTypedValue was correctly restored");
ok(!browser.didStartLoadSinceLastUserTyping(),
"We still know that no load is ongoing");
// didStartLoadSinceLastUserTyping does not exist on lazy tabs.
if (browser.didStartLoadSinceLastUserTyping) {
ok(!browser.didStartLoadSinceLastUserTyping(),
"We still know that no load is ongoing");
}
is(gURLBar.value, "about:mozilla",
"Address bar's value correctly restored");
// Change tabs to make sure address bar value gets updated
gBrowser.selectedTab = gBrowser.tabContainer.getItemAtIndex(1);
is(gURLBar.value, "example.org",
"Address bar's value correctly updated");
runNextTest();
// Change tabs to make sure address bar value gets updated. If tab is
// lazy, wait for SSTabRestored to ensure address bar has time to update.
let tabToSelect = gBrowser.tabContainer.getItemAtIndex(1);
if (tabToSelect.linkedBrowser.isConnected) {
gBrowser.selectedTab = tabToSelect;
is(gURLBar.value, "example.org",
"Address bar's value correctly updated");
runNextTest();
} else {
gBrowser.tabContainer.addEventListener("SSTabRestored",
function SSTabRestored(event) {
if (event.target == tabToSelect) {
gBrowser.tabContainer.removeEventListener("SSTabRestored", SSTabRestored, true);
is(gURLBar.value, "example.org",
"Address bar's value correctly updated");
runNextTest();
}
}, true);
gBrowser.selectedTab = tabToSelect;
}
});
}

View File

@ -5,10 +5,9 @@
var stateBackup = ss.getBrowserState();
function cleanup() {
// Reset the pref
try {
Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
} catch (e) {}
// Reset the prefs
Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
Services.prefs.clearUserPref("browser.sessionstore.restore_tabs_lazily");
ss.setBrowserState(stateBackup);
executeSoon(finish);
}
@ -20,6 +19,8 @@ function test() {
// Set the pref to true so we know exactly how many tabs should be restoring at
// any given time. This guarantees that a finishing load won't start another.
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
// Don't restore tabs lazily.
Services.prefs.setBoolPref("browser.sessionstore.restore_tabs_lazily", false);
let state = { windows: [{ tabs: [
{ entries: [{ url: "http://example.org/#1", triggeringPrincipal_base64 }] },

View File

@ -9,6 +9,8 @@ add_task(function* () {
// Set the pref to true so we know exactly how many tabs should be restoring at
// any given time. This guarantees that a finishing load won't start another.
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
// Don't restore tabs lazily.
Services.prefs.setBoolPref("browser.sessionstore.restore_tabs_lazily", false);
let state = { windows: [{ tabs: [
{ entries: [{ url: "http://example.org#1", triggeringPrincipal_base64 }], extData: { "uniq": r() } },
@ -92,5 +94,7 @@ add_task(function* () {
yield progressCallback();
// Cleanup.
Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
Services.prefs.clearUserPref("browser.sessionstore.restore_tabs_lazily");
yield promiseBrowserState(stateBackup);
});

View File

@ -97,22 +97,26 @@ function test_setTabState() {
ss.setTabValue(tab, "baz", "qux");
}
function onSSTabRestored(aEvent) {
is(busyEventCount, 1);
is(readyEventCount, 1);
is(ss.getTabValue(tab, "baz"), "qux");
is(tab.linkedBrowser.currentURI.spec, "http://example.org/");
function onSSTabRestoring(aEvent) {
if (aEvent.target == tab) {
is(busyEventCount, 1);
is(readyEventCount, 1);
is(ss.getTabValue(tab, "baz"), "qux");
is(tab.linkedBrowser.currentURI.spec, "http://example.org/");
window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy);
window.removeEventListener("SSWindowStateReady", onSSWindowStateReady);
gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored);
window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy);
window.removeEventListener("SSWindowStateReady", onSSWindowStateReady);
gBrowser.tabContainer.removeEventListener("SSTabRestoring", onSSTabRestoring);
runNextTest();
runNextTest();
}
}
window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy);
window.addEventListener("SSWindowStateReady", onSSWindowStateReady);
gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored);
gBrowser.tabContainer.addEventListener("SSTabRestoring", onSSTabRestoring);
// Browser must be inserted in order to restore.
gBrowser._insertBrowser(tab);
ss.setTabState(tab, newTabState);
}
@ -137,23 +141,26 @@ function test_duplicateTab() {
ss.setTabValue(newTab, "baz", "qux");
}
function onSSTabRestored(aEvent) {
is(busyEventCount, 1);
is(readyEventCount, 1);
is(ss.getTabValue(newTab, "baz"), "qux");
is(newTab.linkedBrowser.currentURI.spec, "about:rights");
function onSSTabRestoring(aEvent) {
if (aEvent.target == newTab) {
is(busyEventCount, 1);
is(readyEventCount, 1);
is(ss.getTabValue(newTab, "baz"), "qux");
is(newTab.linkedBrowser.currentURI.spec, "about:rights");
window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy);
window.removeEventListener("SSWindowStateReady", onSSWindowStateReady);
gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored);
window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy);
window.removeEventListener("SSWindowStateReady", onSSWindowStateReady);
gBrowser.tabContainer.removeEventListener("SSTabRestoring", onSSTabRestoring);
runNextTest();
runNextTest();
}
}
window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy);
window.addEventListener("SSWindowStateReady", onSSWindowStateReady);
gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored);
gBrowser.tabContainer.addEventListener("SSTabRestoring", onSSTabRestoring);
gBrowser._insertBrowser(tab);
newTab = ss.duplicateTab(window, tab);
}

View File

@ -147,7 +147,6 @@ add_task(function* test_scroll_background_tabs() {
// The second tab should be the one we loaded URL at still
tab = newWin.gBrowser.tabs[1];
yield promiseTabRestoring(tab);
ok(tab.hasAttribute("pending"), "Tab should be pending");
browser = tab.linkedBrowser;

View File

@ -47,7 +47,6 @@ add_task(function* test_scroll_background_about_reader_tabs() {
// The second tab should be the one we loaded URL at still
tab = newWin.gBrowser.tabs[1];
yield promiseTabRestoring(tab);
ok(tab.hasAttribute("pending"), "Tab should be pending");
browser = tab.linkedBrowser;

View File

@ -94,18 +94,24 @@ function waitForBrowserState(aState, aSetStateCallback) {
let windowObserving = false;
let restoreHiddenTabs = Services.prefs.getBoolPref(
"browser.sessionstore.restore_hidden_tabs");
let restoreTabsLazily = Services.prefs.getBoolPref(
"browser.sessionstore.restore_tabs_lazily");
aState.windows.forEach(function(winState) {
winState.tabs.forEach(function(tabState) {
if (restoreHiddenTabs || !tabState.hidden)
if (!restoreTabsLazily && (restoreHiddenTabs || !tabState.hidden))
expectedTabsRestored++;
});
});
// There must be only hidden tabs and restoreHiddenTabs = false. We still
// If there are only hidden tabs and restoreHiddenTabs = false, we still
// expect one of them to be restored because it gets shown automatically.
if (!expectedTabsRestored)
// Otherwise if lazy tab restore there will only be one tab restored per window.
if (!expectedTabsRestored) {
expectedTabsRestored = 1;
} else if (restoreTabsLazily) {
expectedTabsRestored = aState.windows.length;
}
function onSSTabRestored(aEvent) {
if (++tabsRestored == expectedTabsRestored) {
@ -376,11 +382,11 @@ var gProgressListener = {
for (let win of BrowserWindowIterator()) {
for (let i = 0; i < win.gBrowser.tabs.length; i++) {
let browser = win.gBrowser.tabs[i].linkedBrowser;
if (!browser.__SS_restoreState)
if (browser.isConnected && !browser.__SS_restoreState)
wasRestored++;
else if (browser.__SS_restoreState == TAB_STATE_RESTORING)
isRestoring++;
else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE || !browser.isConnected)
needsRestore++;
}
}

View File

@ -92,12 +92,22 @@ var BrowserHelper = {
},
increasePriority: function NP_BH_increasePriority(aBrowser) {
// Ignore if browser is lazy. Once the browser is instantiated, this will
// get taken care of by TabBrowserInserted and TabSelect handlers.
if (!aBrowser.isConnected) {
return;
}
aBrowser.adjustPriority(PRIORITY_DELTA);
_priorityBackup.set(aBrowser.permanentKey,
_priorityBackup.get(aBrowser.permanentKey) + PRIORITY_DELTA);
},
decreasePriority: function NP_BH_decreasePriority(aBrowser) {
// Ignore if browser is lazy. Once the browser is instantiated, this will
// get taken care of by TabBrowserInserted and TabSelect handlers.
if (!aBrowser.isConnected) {
return;
}
aBrowser.adjustPriority(PRIORITY_DELTA * -1);
_priorityBackup.set(aBrowser.permanentKey,
_priorityBackup.get(aBrowser.permanentKey) - PRIORITY_DELTA);

View File

@ -355,7 +355,8 @@ BrowserTabList.prototype.getTab = function ({ outerWindowID, tabId }) {
} else if (typeof tabId == "number") {
// Tabs OOP
for (let browser of this._getBrowsers()) {
if (browser.frameLoader.tabParent &&
if (browser.frameLoader &&
browser.frameLoader.tabParent &&
browser.frameLoader.tabParent.tabId === tabId) {
return this._getActorForBrowser(browser);
}

View File

@ -564,6 +564,11 @@ this.BrowserTestUtils = {
if (winType == "navigator:browser") {
let finalMsgsPromise = new Promise((resolve) => {
let browserSet = new Set(win.gBrowser.browsers);
// Ensure all browsers have been inserted or we won't get
// messages back from them.
browserSet.forEach((browser) => {
win.gBrowser._insertBrowser(win.gBrowser.getTabForBrowser(browser));
})
let mm = win.getGroupMessageManager("browsers");
mm.addMessageListener("SessionStore:update", function onMessage(msg) {

View File

@ -35,10 +35,11 @@ this.PrivateBrowsingUtils = {
isBrowserPrivate(aBrowser) {
let chromeWin = aBrowser.ownerGlobal;
if (chromeWin.gMultiProcessBrowser) {
if (chromeWin.gMultiProcessBrowser || !aBrowser.isConnected) {
// In e10s we have to look at the chrome window's private
// browsing status since the only alternative is to check the
// content window, which is in another process.
// content window, which is in another process. If the browser
// is lazy then the content window doesn't exist.
return this.isWindowPrivate(chromeWin);
}
return this.privacyContextFromWindow(aBrowser.contentWindow).usePrivateBrowsing;