author | beekill <nnn_bikiu0707@yahoo.com> |
Fri, 21 Jul 2017 10:00:32 +0700 | |
changeset 379718 | 1576132e98a33b5034f1cca833c8fa4a8ab5e4d2 |
parent 379717 | 6af4edb9979ef0c3f2d6ae9fe0afc9249d34c32c |
child 379719 | b9fdf5d1b40222f6a6dcf41334d643a43d6b40ed |
push id | 32461 |
push user | kwierso@gmail.com |
push date | Fri, 08 Sep 2017 20:15:32 +0000 |
treeherder | mozilla-central@dd3736e98e4e [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mikedeboer |
bugs | 1034036 |
milestone | 57.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
|
--- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -203,16 +203,25 @@ function debug(aMsg) { /** * A global value to tell that fingerprinting resistance is enabled or not. * If it's enabled, the session restore won't restore the window's size and * size mode. * This value is controlled by preference privacy.resistFingerprinting. */ var gResistFingerprintingEnabled = false; +/** + * Return a promise that will be resolved once it receives event + * |SSBrowserWindowShowing| which is dispatched in onBrowserWindowShown. + */ +function promiseWindowShowing(window) { + return new Promise(resolve => window.addEventListener("SSBrowserWindowShowing", + () => resolve(window), {once: true})); +} + this.SessionStore = { get promiseInitialized() { return SessionStoreInternal.promiseInitialized; }, get promiseAllWindowsRestored() { return SessionStoreInternal.promiseAllWindowsRestored; }, @@ -687,18 +696,19 @@ var SessionStoreInternal = { // Update the session start time using the restored session state. this._updateSessionStartTime(state); // make sure that at least the first window doesn't have anything hidden delete state.windows[0].hidden; // Since nothing is hidden in the first window, it cannot be a popup delete state.windows[0].isPopup; - // We don't want to minimize and then open a window at startup. - if (state.windows[0].sizemode == "minimized") + // We don't want to minimize and then open a window at startup. However, + // when we're restoring, we should preserve previous windows sizemode. + if (state.windows[0].sizemode == "minimized" && !ss.doRestore()) state.windows[0].sizemode = "normal"; // clear any lastSessionWindowID attributes since those don't matter // during normal restore state.windows.forEach(function(aWindow) { delete aWindow.__lastSessionWindowID; }); } } catch (ex) { debug("The session file is invalid: " + ex); } @@ -1174,19 +1184,21 @@ var SessionStoreInternal = { } else { // Nothing to restore, notify observers things are complete. Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED); Services.obs.notifyObservers(null, "sessionstore-one-or-no-tab-restored"); this._deferredAllWindowsRestored.resolve(); } // this window was opened by _openWindowWithState } else if (!this._isWindowLoaded(aWindow)) { - let state = this._statesToRestore[aWindow.__SS_restoreID]; - let options = {overwriteTabs: true, isFollowUp: state.windows.length == 1}; - this.restoreWindow(aWindow, state.windows[0], options); + // We used to restore window when it is opened. However, we + // want to restore windows after all windows are opened + // (bug 1034036). So the restoration process has been moved to + // function restoreWindowsFeaturesAndTabs. + // The user opened another, non-private window after starting up with // a single private one. Let's restore the session we actually wanted to // restore at startup. } else if (this._deferredInitialState && !isPrivateWindow && aWindow.toolbar.visible) { // global data must be restored before restoreWindow is called so that // it happens before observers are notified @@ -1268,16 +1280,20 @@ var SessionStoreInternal = { * Called right before a new browser window is shown. * @param aWindow * Window reference */ onBeforeBrowserWindowShown(aWindow) { // Register the window. this.onLoad(aWindow); + // Dispatch a custom event to tell that a new window is about to be shown. + let evt = new aWindow.CustomEvent("SSBrowserWindowShowing"); + aWindow.dispatchEvent(evt); + // Just call initializeWindow() directly if we're initialized already. if (this._sessionInitialized) { this.initializeWindow(aWindow); return; } // The very first window that is opened creates a promise that is then // re-used by all subsequent windows. The promise will be used to tell @@ -1342,17 +1358,17 @@ var SessionStoreInternal = { let completionPromise = Promise.resolve(); // this window was about to be restored - conserve its original data, if any let isFullyLoaded = this._isWindowLoaded(aWindow); if (!isFullyLoaded) { if (!aWindow.__SSi) { aWindow.__SSi = this._generateWindowID(); } - this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID]; + this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID].windows[0]; delete this._statesToRestore[aWindow.__SS_restoreID]; delete aWindow.__SS_restoreID; } // ignore windows not tracked by SessionStore if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) { return completionPromise; } @@ -2508,16 +2524,17 @@ var SessionStoreInternal = { } // reopen the window let state = { windows: this._removeClosedWindow(aIndex) }; delete state.windows[0].closedAt; // Window is now open. let window = this._openWindowWithState(state); this.windowToFocus = window; + promiseWindowShowing(window).then(win => this.restoreWindows(win, state, {overwriteTabs: true})); // Notify of changes to closed objects. this._notifyOfClosedObjectsChange(); return window; }, forgetClosedWindow: function ssi_forgetClosedWindow(aIndex) { @@ -2761,16 +2778,19 @@ var SessionStoreInternal = { // global data must be restored before restoreWindow is called so that // it happens before observers are notified this._globalState.setFromState(lastSessionState); // Restore session cookies. SessionCookies.restore(lastSessionState.cookies || []); + let openedWindows = []; + let remainingWindows = []; + // Restore into windows or open new ones as needed. for (let i = 0; i < lastSessionState.windows.length; i++) { let winState = lastSessionState.windows[i]; let lastSessionWindowID = winState.__lastSessionWindowID; // delete lastSessionWindowID so we don't add that to the window again delete winState.__lastSessionWindowID; // See if we can use an open window. First try one that is associated with @@ -2789,27 +2809,38 @@ var SessionStoreInternal = { // Since we're not overwriting existing tabs, we want to merge _closedTabs, // putting existing ones first. Then make sure we're respecting the max pref. if (winState._closedTabs && winState._closedTabs.length) { let curWinState = this._windows[windowToUse.__SSi]; curWinState._closedTabs = curWinState._closedTabs.concat(winState._closedTabs); curWinState._closedTabs.splice(this._max_tabs_undo, curWinState._closedTabs.length); } - // Restore into that window - pretend it's a followup since we'll already - // have a focused window. // XXXzpao This is going to merge extData together (taking what was in // winState over what is in the window already. - let options = {overwriteTabs: canOverwriteTabs, isFollowUp: true}; - this.restoreWindow(windowToUse, winState, options); + // We don't restore window right away, just store its data. + // Later, these windows will be restored with newly opened windows. + if ("zIndex" in winState) { + windowToUse.__SS_zIndex = winState.zIndex; + } + + this._updateWindowRestoreState(windowToUse, {windows: [winState]}); + windowToUse.__SS_restoreOptions = {overwriteTabs: canOverwriteTabs}; + openedWindows.push(windowToUse); } else { - this._openWindowWithState({ windows: [winState] }); + remainingWindows.push(winState); } } + // Actually restore windows in reversed z-order. + this.openWindows({windows: remainingWindows}).then(wins => { + wins = openedWindows.concat(wins); + this.restoreWindowsInReversedZOrder(wins, this._statesToRestore, false); + }); + // Merge closed windows from this session with ones from last session if (lastSessionState._closedWindows) { this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows); this._capClosedWindows(); this._closedObjectsChanged = true; } DevToolsShim.restoreDevToolsSession(lastSessionState); @@ -3051,27 +3082,49 @@ var SessionStoreInternal = { } return [true, canOverwriteTabs]; }, /* ........ Saving Functionality .............. */ /** + * Return z-index of a window. + * If a window is minimized, its z-index is -1. + * + * @param aWindow + * Window reference + * @return z-index of that window + */ + _getZIndex(window) { + let isMinimized = this._getWindowDimension(window, "sizemode") == "minimized"; + return isMinimized ? -1 : window.__SS_zIndex; + }, + + /** * Store window dimensions, visibility, sidebar * @param aWindow * Window reference */ _updateWindowFeatures: function ssi_updateWindowFeatures(aWindow) { var winData = this._windows[aWindow.__SSi]; WINDOW_ATTRIBUTES.forEach(function(aAttr) { winData[aAttr] = this._getWindowDimension(aWindow, aAttr); }, this); + // We only update zIndex when a valid zIndex is found in a window. + // There will be a case (flushAllWindowsAsync) where this function + // is called outside |forEachBrowserWindow|, therefore, no zIndex + // is found. + let zIndex = this._getZIndex(aWindow); + if (zIndex) { + winData.zIndex = this._getZIndex(aWindow); + } + var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) { return aWindow[aItem] && !aWindow[aItem].visible; }); if (hidden.length != 0) winData.hidden = hidden.join(","); else if (winData.hidden) delete winData.hidden; @@ -3256,37 +3309,49 @@ var SessionStoreInternal = { DirtyWindows.remove(aWindow); return tabMap; }, /* ........ Restoring Functionality .............. */ /** + * Open windows with data + * + * @param root + * Windows data + * @returns a promise resolved when all windows have been opened + */ + openWindows(root) { + let openedWindowPromises = []; + for (let winData of root.windows) { + if (winData && winData.tabs && winData.tabs[0]) { + let window = this._openWindowWithState({ windows: [winData] }); + openedWindowPromises.push(promiseWindowShowing(window)); + } + } + return Promise.all(openedWindowPromises); + }, + + /** * restore features to a single window * @param aWindow * Window reference to the window to use for restoration * @param winData * JS object * @param aOptions * {overwriteTabs: true} to overwrite existing tabs w/ new ones - * {isFollowUp: true} if this is not the restoration of the 1st window * {firstWindow: true} if this is the first non-private window we're * restoring in this session, that might open an * external link as well */ restoreWindow: function ssi_restoreWindow(aWindow, winData, aOptions = {}) { let overwriteTabs = aOptions && aOptions.overwriteTabs; - let isFollowUp = aOptions && aOptions.isFollowUp; let firstWindow = aOptions && aOptions.firstWindow; - if (isFollowUp) { - this.windowToFocus = aWindow; - } - // initialize window if necessary if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi])) this.onLoad(aWindow); TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS"); // We're not returning from this before we end up calling restoreTabs // for this window, so make sure we send the SSWindowStateBusy event. @@ -3413,17 +3478,16 @@ var SessionStoreInternal = { // 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) aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID; if (overwriteTabs) { - this.restoreWindowFeatures(aWindow, winData); delete this._windows[aWindow.__SSi].extData; } // Restore cookies from legacy sessions, i.e. before bug 912717. SessionCookies.restore(winData.cookies || []); if (winData.extData) { if (!this._windows[aWindow.__SSi].extData) { @@ -3512,36 +3576,78 @@ var SessionStoreInternal = { } // A flag indicate that we've prepared a connection for this tab and // if is called again, we shouldn't prepare another connection. tab.__SS_connectionPrepared = true; } }, /** + * This function will restore window features and then retore window data. + * + * @param windows + * array of windows to be restored into + * @param statesToRestore + * states of windows to be restored + */ + restoreWindowsFeaturesAndTabs(windows, statesToRestore) { + // First, we restore window features, so that when users + // interacting with a window, we don't steal the window focus. + windows.forEach((window) => { + let state = statesToRestore[window.__SS_restoreID]; + this.restoreWindowFeatures(window, state.windows[0]); + }); + + // Then we restore data into windows. + for (let i = 0; i < windows.length; ++i) { + let state = statesToRestore[windows[i].__SS_restoreID]; + let option = windows[i].__SS_restoreOptions || {overwriteTabs: true}; + this.restoreWindow(windows[i], state.windows[0], option); + delete windows[i].__SS_restoreOptions; + delete windows[i].__SS_zIndex; + } + }, + + /** + * This function will restore window in reversed z-index, so that users + * will be presented with most recently used window first. + * + * @param windows + * array of windows to be restored into + * @param statesToRestore + * states of windows to be restored + * @param areFollowUps + * a flag indicate these windows are follow-up windows + */ + restoreWindowsInReversedZOrder(windows, statesToRestore, areFollowUps) { + if (windows.some(window => !!window.__SS_zIndex)) { + windows.sort((a, b) => b.__SS_zIndex - a.__SS_zIndex); + } + + if (!areFollowUps) { + this.windowToFocus = windows[0]; + } + + this.restoreWindowsFeaturesAndTabs(windows, statesToRestore); + }, + + /** * Restore multiple windows using the provided state. * @param aWindow * Window reference to the first window to use for restoration. * Additionally required windows will be opened. * @param aState * JS object or JSON string * @param aOptions * {overwriteTabs: true} to overwrite existing tabs w/ new ones - * {isFollowUp: true} if this is not the restoration of the 1st window * {firstWindow: true} if this is the first non-private window we're * restoring in this session, that might open an * external link as well */ restoreWindows: function ssi_restoreWindows(aWindow, aState, aOptions = {}) { - let isFollowUp = aOptions && aOptions.isFollowUp; - - if (isFollowUp) { - this.windowToFocus = aWindow; - } - // initialize window if necessary if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi])) this.onLoad(aWindow); let root; try { root = (typeof aState == "string") ? JSON.parse(aState) : aState; } catch (ex) { // invalid state object - don't restore anything @@ -3557,34 +3663,38 @@ var SessionStoreInternal = { } // We're done here if there are no windows. if (!root.windows || !root.windows.length) { this._sendRestoreCompletedNotifications(); return; } - if (!root.selectedWindow || root.selectedWindow > root.windows.length) { - root.selectedWindow = 0; - } - - // open new windows for all further window entries of a multi-window session - // (unless they don't contain any tab data) - let winData; - for (var w = 1; w < root.windows.length; w++) { - winData = root.windows[w]; - if (winData && winData.tabs && winData.tabs[0]) { - var window = this._openWindowWithState({ windows: [winData] }); - if (w == root.selectedWindow - 1) { - this.windowToFocus = window; - } - } - } - - this.restoreWindow(aWindow, root.windows[0], aOptions); + // Store z-index to current window so that it can be restored in reversed z-order. + let firstWindowData = root.windows.splice(0, 1); + if ("zIndex" in firstWindowData[0]) { + aWindow.__SS_zIndex = firstWindowData[0].zIndex; + } + + // Store the restore state and restore option of the current window, + // so that the window can be restored in reversed z-order. + this._updateWindowRestoreState(aWindow, {windows: firstWindowData}); + aWindow.__SS_restoreOptions = aOptions; + + // Begin the restoration: First open all windows in creation order. + // After all windows are opened, we restore states to windows in + // reversed z-order. + this.openWindows(root).then(windows => { + // We want to add current window to opened window, so that this window will be + // restored in reversed z-order. (We add the window to first position, in case + // no z-indices are found, that window will be restored first.) + windows.unshift(aWindow); + + this.restoreWindowsInReversedZOrder(windows, this._statesToRestore, false); + }); DevToolsShim.restoreDevToolsSession(aState); }, /** * Manage history restoration for a window * @param aWindow * Window to restore the tabs into @@ -4162,28 +4272,52 @@ var SessionStoreInternal = { _updateSessionStartTime: function ssi_updateSessionStartTime(state) { // Attempt to load the session start time from the session state if (state.session && state.session.startTime) { this._sessionStartTime = state.session.startTime; } }, /** - * call a callback for all currently opened browser windows + * A boolean flag indicates whether we can iterate over all windows + * in their z order. + */ + get isWMZOrderBroken() { + let broken_wm_z_order = AppConstants.platform != "macosx" && AppConstants.platform != "win"; + delete this.isWMZOrderBroken; + return this.isWMZOrderBroken = broken_wm_z_order; + }, + + /** + * Call a callback for all currently opened browser windows. + * This will iterate the windows in z-index from front to back, + * and assign z-index to the window. * (might miss the most recent one) * @param aFunc * Callback each window is passed to */ _forEachBrowserWindow: function ssi_forEachBrowserWindow(aFunc) { - var windowsEnum = Services.wm.getEnumerator("navigator:browser"); - + let windowsEnum = this.isWMZOrderBroken ? + Services.wm.getEnumerator("navigator:browser") : + Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", false); + let mostRecentWindow = this.isWMZOrderBroken ? this._getMostRecentBrowserWindow() : null; + + // We want to start zIndex at 1, so that, in _updateWindowFeatures, if no z-index is found + // in a window, we can just check with a simple condition if: `if (zIndex)`. + let zIndex = 1; while (windowsEnum.hasMoreElements()) { - var window = windowsEnum.getNext(); + let window = windowsEnum.getNext(); if (window.__SSi && !window.closed) { + if (this.isWMZOrderBroken) { + window.__SS_zIndex = mostRecentWindow.__SSi === window.__SSi ? 2 : 1; + } else { + window.__SS_zIndex = zIndex++; + } aFunc.call(this, window); + delete window.__SS_zIndex; } } }, /** * Returns most recent window * @returns Window reference */ @@ -4205,16 +4339,33 @@ var SessionStoreInternal = { if (window.closed) { promises.push(this.onClose(window)); } } return Promise.all(promises); }, /** + * Store a restore state of a window to this._statesToRestore. The window + * will be given an id that can be used to get the restore state from + * this._statesToRestore. + * + * @param window + * a reference to a window that has a state to restore + * @param state + * an object containing session data + */ + _updateWindowRestoreState(window, state) { + do { + var ID = "window" + Math.random(); + } while (ID in this._statesToRestore); + this._statesToRestore[(window.__SS_restoreID = ID)] = state; + }, + + /** * open a new browser window for a given session state * called when restoring a multi-window session * @param aState * Object containing session data */ _openWindowWithState: function ssi_openWindowWithState(aState) { var argString = Cc["@mozilla.org/supports-string;1"]. createInstance(Ci.nsISupportsString); @@ -4232,20 +4383,22 @@ var SessionStoreInternal = { if (winState.isPrivate) { features += ",private"; } var window = Services.ww.openWindow(null, this._prefBranch.getCharPref("chromeURL"), "_blank", features, argString); - do { - var ID = "window" + Math.random(); - } while (ID in this._statesToRestore); - this._statesToRestore[(window.__SS_restoreID = ID)] = aState; + // Store z-index, so that windows can be restored in reversed z-order. + if ("zIndex" in aState.windows[0]) { + window.__SS_zIndex = aState.windows[0].zIndex; + } + + this._updateWindowRestoreState(window, aState); return window; }, /** * Whether or not to resume session, if not recovering from a crash. * @returns bool */