Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
* You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
"use strict";
6
7
var EXPORTED_SYMBOLS = ["SessionStore"];
8
9
// Current version of the format used by Session Restore.
10
const FORMAT_VERSION = 1;
11
12
const TAB_CUSTOM_VALUES = new WeakMap();
13
const TAB_LAZY_STATES = new WeakMap();
14
const TAB_STATE_NEEDS_RESTORE = 1;
15
const TAB_STATE_RESTORING = 2;
16
const TAB_STATE_WILL_RESTORE = 3;
17
const TAB_STATE_FOR_BROWSER = new WeakMap();
18
const WINDOW_RESTORE_IDS = new WeakMap();
19
const WINDOW_RESTORE_ZINDICES = new WeakMap();
20
const WINDOW_SHOWING_PROMISES = new Map();
21
22
// A new window has just been restored. At this stage, tabs are generally
23
// not restored.
24
const NOTIFY_SINGLE_WINDOW_RESTORED = "sessionstore-single-window-restored";
25
const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
26
const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
27
const NOTIFY_LAST_SESSION_CLEARED = "sessionstore-last-session-cleared";
28
const NOTIFY_RESTORING_ON_STARTUP = "sessionstore-restoring-on-startup";
29
const NOTIFY_INITIATING_MANUAL_RESTORE =
30
"sessionstore-initiating-manual-restore";
31
const NOTIFY_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed";
32
33
const NOTIFY_TAB_RESTORED = "sessionstore-debug-tab-restored"; // WARNING: debug-only
34
const NOTIFY_DOMWINDOWCLOSED_HANDLED =
35
"sessionstore-debug-domwindowclosed-handled"; // WARNING: debug-only
36
37
// Maximum number of tabs to restore simultaneously. Previously controlled by
38
// the browser.sessionstore.max_concurrent_tabs pref.
39
const MAX_CONCURRENT_TAB_RESTORES = 3;
40
41
// Amount (in CSS px) by which we allow window edges to be off-screen
42
// when restoring a window, before we override the saved position to
43
// pull the window back within the available screen area.
44
const SCREEN_EDGE_SLOP = 8;
45
46
// global notifications observed
47
const OBSERVING = [
48
"browser-window-before-show",
49
"domwindowclosed",
50
"quit-application-granted",
51
"browser-lastwindow-close-granted",
52
"quit-application",
53
"browser:purge-session-history",
54
"browser:purge-session-history-for-domain",
55
"idle-daily",
56
"clear-origin-attributes-data",
57
"http-on-may-change-process",
58
];
59
60
// XUL Window properties to (re)store
61
// Restored in restoreDimensions()
62
const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
63
64
// Hideable window features to (re)store
65
// Restored in restoreWindowFeatures()
66
const WINDOW_HIDEABLE_FEATURES = [
67
"menubar",
68
"toolbar",
69
"locationbar",
70
"personalbar",
71
"statusbar",
72
"scrollbars",
73
];
74
75
// Messages that will be received via the Frame Message Manager.
76
const MESSAGES = [
77
// The content script sends us data that has been invalidated and needs to
78
// be saved to disk.
79
"SessionStore:update",
80
81
// The restoreHistory code has run. This is a good time to run SSTabRestoring.
82
"SessionStore:restoreHistoryComplete",
83
84
// The load for the restoring tab has begun. We update the URL bar at this
85
// time; if we did it before, the load would overwrite it.
86
"SessionStore:restoreTabContentStarted",
87
88
// All network loads for a restoring tab are done, so we should
89
// consider restoring another tab in the queue. The document has
90
// been restored, and forms have been filled. We trigger
91
// SSTabRestored at this time.
92
"SessionStore:restoreTabContentComplete",
93
94
// A crashed tab was revived by navigating to a different page. Remove its
95
// browser from the list of crashed browsers to stop ignoring its messages.
96
"SessionStore:crashedTabRevived",
97
98
// The content script encountered an error.
99
"SessionStore:error",
100
];
101
102
// The list of messages we accept from <xul:browser>s that have no tab
103
// assigned, or whose windows have gone away. Those are for example the
104
// ones that preload about:newtab pages, or from browsers where the window
105
// has just been closed.
106
const NOTAB_MESSAGES = new Set([
107
// For a description see above.
108
"SessionStore:crashedTabRevived",
109
110
// For a description see above.
111
"SessionStore:update",
112
113
// For a description see above.
114
"SessionStore:error",
115
]);
116
117
// The list of messages we accept without an "epoch" parameter.
118
// See getCurrentEpoch() and friends to find out what an "epoch" is.
119
const NOEPOCH_MESSAGES = new Set([
120
// For a description see above.
121
"SessionStore:crashedTabRevived",
122
123
// For a description see above.
124
"SessionStore:error",
125
]);
126
127
// The list of messages we want to receive even during the short period after a
128
// frame has been removed from the DOM and before its frame script has finished
129
// unloading.
130
const CLOSED_MESSAGES = new Set([
131
// For a description see above.
132
"SessionStore:crashedTabRevived",
133
134
// For a description see above.
135
"SessionStore:update",
136
137
// For a description see above.
138
"SessionStore:error",
139
]);
140
141
// These are tab events that we listen to.
142
const TAB_EVENTS = [
143
"TabOpen",
144
"TabBrowserInserted",
145
"TabClose",
146
"TabSelect",
147
"TabShow",
148
"TabHide",
149
"TabPinned",
150
"TabUnpinned",
151
];
152
154
155
/**
156
* When calling restoreTabContent, we can supply a reason why
157
* the content is being restored. These are those reasons.
158
*/
159
const RESTORE_TAB_CONTENT_REASON = {
160
/**
161
* SET_STATE:
162
* We're restoring this tab's content because we're setting
163
* state inside this browser tab, probably because the user
164
* has asked us to restore a tab (or window, or entire session).
165
*/
166
SET_STATE: 0,
167
/**
168
* NAVIGATE_AND_RESTORE:
169
* We're restoring this tab's content because a navigation caused
170
* us to do a remoteness-flip.
171
*/
172
NAVIGATE_AND_RESTORE: 1,
173
};
174
175
// 'browser.startup.page' preference value to resume the previous session.
176
const BROWSER_STARTUP_RESUME_SESSION = 3;
177
178
ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
179
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
180
ChromeUtils.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
181
ChromeUtils.import("resource://gre/modules/Timer.jsm", this);
182
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
183
ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
184
185
XPCOMUtils.defineLazyServiceGetters(this, {
186
gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
187
Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
188
});
189
190
XPCOMUtils.defineLazyModuleGetters(this, {
193
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
213
});
214
215
/**
216
* |true| if we are in debug mode, |false| otherwise.
217
* Debug mode is controlled by preference browser.sessionstore.debug
218
*/
219
var gDebuggingEnabled = false;
220
function debug(aMsg) {
221
if (gDebuggingEnabled) {
222
aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
223
Services.console.logStringMessage(aMsg);
224
}
225
}
226
227
/**
228
* A global value to tell that fingerprinting resistance is enabled or not.
229
* If it's enabled, the session restore won't restore the window's size and
230
* size mode.
231
* This value is controlled by preference privacy.resistFingerprinting.
232
*/
233
var gResistFingerprintingEnabled = false;
234
235
var SessionStore = {
236
get promiseInitialized() {
237
return SessionStoreInternal.promiseInitialized;
238
},
239
240
get promiseAllWindowsRestored() {
241
return SessionStoreInternal.promiseAllWindowsRestored;
242
},
243
244
get canRestoreLastSession() {
245
return SessionStoreInternal.canRestoreLastSession;
246
},
247
248
set canRestoreLastSession(val) {
249
SessionStoreInternal.canRestoreLastSession = val;
250
},
251
252
get lastClosedObjectType() {
253
return SessionStoreInternal.lastClosedObjectType;
254
},
255
256
get willAutoRestore() {
257
return SessionStoreInternal.willAutoRestore;
258
},
259
260
init: function ss_init() {
261
SessionStoreInternal.init();
262
},
263
264
getBrowserState: function ss_getBrowserState() {
265
return SessionStoreInternal.getBrowserState();
266
},
267
268
setBrowserState: function ss_setBrowserState(aState) {
269
SessionStoreInternal.setBrowserState(aState);
270
},
271
272
getWindowState: function ss_getWindowState(aWindow) {
273
return SessionStoreInternal.getWindowState(aWindow);
274
},
275
276
setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite) {
277
SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite);
278
},
279
280
getTabState: function ss_getTabState(aTab) {
281
return SessionStoreInternal.getTabState(aTab);
282
},
283
284
setTabState: function ss_setTabState(aTab, aState) {
285
SessionStoreInternal.setTabState(aTab, aState);
286
},
287
288
getInternalObjectState(obj) {
289
return SessionStoreInternal.getInternalObjectState(obj);
290
},
291
292
duplicateTab: function ss_duplicateTab(
293
aWindow,
294
aTab,
295
aDelta = 0,
296
aRestoreImmediately = true
297
) {
298
return SessionStoreInternal.duplicateTab(
299
aWindow,
300
aTab,
301
aDelta,
302
aRestoreImmediately
303
);
304
},
305
306
getClosedTabCount: function ss_getClosedTabCount(aWindow) {
307
return SessionStoreInternal.getClosedTabCount(aWindow);
308
},
309
310
getClosedTabData: function ss_getClosedTabData(aWindow, aAsString = true) {
311
return SessionStoreInternal.getClosedTabData(aWindow, aAsString);
312
},
313
314
undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
315
return SessionStoreInternal.undoCloseTab(aWindow, aIndex);
316
},
317
318
forgetClosedTab: function ss_forgetClosedTab(aWindow, aIndex) {
319
return SessionStoreInternal.forgetClosedTab(aWindow, aIndex);
320
},
321
322
getClosedWindowCount: function ss_getClosedWindowCount() {
323
return SessionStoreInternal.getClosedWindowCount();
324
},
325
326
getClosedWindowData: function ss_getClosedWindowData(aAsString = true) {
327
return SessionStoreInternal.getClosedWindowData(aAsString);
328
},
329
330
undoCloseWindow: function ss_undoCloseWindow(aIndex) {
331
return SessionStoreInternal.undoCloseWindow(aIndex);
332
},
333
334
forgetClosedWindow: function ss_forgetClosedWindow(aIndex) {
335
return SessionStoreInternal.forgetClosedWindow(aIndex);
336
},
337
338
getCustomWindowValue(aWindow, aKey) {
339
return SessionStoreInternal.getCustomWindowValue(aWindow, aKey);
340
},
341
342
setCustomWindowValue(aWindow, aKey, aStringValue) {
343
SessionStoreInternal.setCustomWindowValue(aWindow, aKey, aStringValue);
344
},
345
346
deleteCustomWindowValue(aWindow, aKey) {
347
SessionStoreInternal.deleteCustomWindowValue(aWindow, aKey);
348
},
349
350
getCustomTabValue(aTab, aKey) {
351
return SessionStoreInternal.getCustomTabValue(aTab, aKey);
352
},
353
354
setCustomTabValue(aTab, aKey, aStringValue) {
355
SessionStoreInternal.setCustomTabValue(aTab, aKey, aStringValue);
356
},
357
358
deleteCustomTabValue(aTab, aKey) {
359
SessionStoreInternal.deleteCustomTabValue(aTab, aKey);
360
},
361
362
getLazyTabValue(aTab, aKey) {
363
return SessionStoreInternal.getLazyTabValue(aTab, aKey);
364
},
365
366
getCustomGlobalValue(aKey) {
367
return SessionStoreInternal.getCustomGlobalValue(aKey);
368
},
369
370
setCustomGlobalValue(aKey, aStringValue) {
371
SessionStoreInternal.setCustomGlobalValue(aKey, aStringValue);
372
},
373
374
deleteCustomGlobalValue(aKey) {
375
SessionStoreInternal.deleteCustomGlobalValue(aKey);
376
},
377
378
persistTabAttribute: function ss_persistTabAttribute(aName) {
379
SessionStoreInternal.persistTabAttribute(aName);
380
},
381
382
restoreLastSession: function ss_restoreLastSession() {
383
SessionStoreInternal.restoreLastSession();
384
},
385
386
speculativeConnectOnTabHover(tab) {
387
SessionStoreInternal.speculativeConnectOnTabHover(tab);
388
},
389
390
getCurrentState(aUpdateAll) {
391
return SessionStoreInternal.getCurrentState(aUpdateAll);
392
},
393
394
reviveCrashedTab(aTab) {
395
return SessionStoreInternal.reviveCrashedTab(aTab);
396
},
397
398
reviveAllCrashedTabs() {
399
return SessionStoreInternal.reviveAllCrashedTabs();
400
},
401
402
navigateAndRestore(tab, loadArguments, historyIndex) {
403
return SessionStoreInternal.navigateAndRestore(
404
tab,
405
loadArguments,
406
historyIndex
407
);
408
},
409
410
updateSessionStoreFromTablistener(aBrowser, aData) {
411
return SessionStoreInternal.updateSessionStoreFromTablistener(
412
aBrowser,
413
aData
414
);
415
},
416
417
getSessionHistory(tab, updatedCallback) {
418
return SessionStoreInternal.getSessionHistory(tab, updatedCallback);
419
},
420
421
undoCloseById(aClosedId, aIncludePrivate) {
422
return SessionStoreInternal.undoCloseById(aClosedId, aIncludePrivate);
423
},
424
425
resetBrowserToLazyState(tab) {
426
return SessionStoreInternal.resetBrowserToLazyState(tab);
427
},
428
429
/**
430
* Determines whether the passed version number is compatible with
431
* the current version number of the SessionStore.
432
*
433
* @param version The format and version of the file, as an array, e.g.
434
* ["sessionrestore", 1]
435
*/
436
isFormatVersionCompatible(version) {
437
if (!version) {
438
return false;
439
}
440
if (!Array.isArray(version)) {
441
// Improper format.
442
return false;
443
}
444
if (version[0] != "sessionrestore") {
445
// Not a Session Restore file.
446
return false;
447
}
448
let number = Number.parseFloat(version[1]);
449
if (Number.isNaN(number)) {
450
return false;
451
}
452
return number <= FORMAT_VERSION;
453
},
454
455
/**
456
* Filters out not worth-saving tabs from a given browser state object.
457
*
458
* @param aState (object)
459
* The browser state for which we remove worth-saving tabs.
460
* The given object will be modified.
461
*/
462
keepOnlyWorthSavingTabs(aState) {
463
for (let i = aState.windows.length - 1; i >= 0; i--) {
464
let win = aState.windows[i];
465
for (let j = win.tabs.length - 1; j >= 0; j--) {
466
let tab = win.tabs[j];
467
if (!SessionStoreInternal._shouldSaveTab(tab)) {
468
win.tabs.splice(j, 1);
469
if (win.selected > j) {
470
win.selected--;
471
}
472
}
473
}
474
if (!win.tabs.length) {
475
aState.windows.splice(i, 1);
476
if (aState.selectedWindow > i) {
477
aState.selectedWindow--;
478
}
479
}
480
}
481
},
482
};
483
484
// Freeze the SessionStore object. We don't want anyone to modify it.
485
Object.freeze(SessionStore);
486
487
var SessionStoreInternal = {
488
QueryInterface: ChromeUtils.generateQI([
489
Ci.nsIObserver,
490
Ci.nsISupportsWeakReference,
491
]),
492
493
_globalState: new GlobalState(),
494
495
// A counter to be used to generate a unique ID for each closed tab or window.
496
_nextClosedId: 0,
497
498
// A monotonic value used to generate a unique ID for each process switch.
499
_switchIdMonotonic: 0,
500
501
// During the initial restore and setBrowserState calls tracks the number of
502
// windows yet to be restored
503
_restoreCount: -1,
504
505
// For each <browser> element, records the current epoch.
506
_browserEpochs: new WeakMap(),
507
508
// Any browsers that fires the oop-browser-crashed event gets stored in
509
// here - that way we know which browsers to ignore messages from (until
510
// they get restored).
511
_crashedBrowsers: new WeakSet(),
512
513
// A map (xul:browser -> FrameLoader) that maps a browser to the last
514
// associated frameLoader we heard about.
515
_lastKnownFrameLoader: new WeakMap(),
516
517
// A map (xul:browser -> object) that maps a browser associated with a
518
// recently closed tab to all its necessary state information we need to
519
// properly handle final update message.
520
_closedTabs: new WeakMap(),
521
522
// A map (xul:browser -> object) that maps a browser associated with a
523
// recently closed tab due to a window closure to the tab state information
524
// that is being stored in _closedWindows for that tab.
525
_closedWindowTabs: new WeakMap(),
526
527
// A set of window data that has the potential to be saved in the _closedWindows
528
// array for the session. We will remove window data from this set whenever
529
// forgetClosedWindow is called for the window, or when session history is
530
// purged, so that we don't accidentally save that data after the flush has
531
// completed. Closed tabs use a more complicated mechanism for this particular
532
// problem. When forgetClosedTab is called, the browser is removed from the
533
// _closedTabs map, so its data is not recorded. In the purge history case,
534
// the closedTabs array per window is overwritten so that once the flush is
535
// complete, the tab would only ever add itself to an array that SessionStore
536
// no longer cares about. Bug 1230636 has been filed to make the tab case
537
// work more like the window case, which is more explicit, and easier to
538
// reason about.
539
_saveableClosedWindowData: new WeakSet(),
540
541
// A map (xul:browser -> object) that maps a browser that is switching
542
// remoteness via navigateAndRestore, to the loadArguments that were
543
// most recently passed when calling navigateAndRestore.
544
_remotenessChangingBrowsers: new WeakMap(),
545
546
// whether a setBrowserState call is in progress
547
_browserSetState: false,
548
549
// time in milliseconds when the session was started (saved across sessions),
550
// defaults to now if no session was restored or timestamp doesn't exist
551
_sessionStartTime: Date.now(),
552
553
// states for all currently opened windows
554
_windows: {},
555
556
// counter for creating unique window IDs
557
_nextWindowID: 0,
558
559
// states for all recently closed windows
560
_closedWindows: [],
561
562
// collection of session states yet to be restored
563
_statesToRestore: {},
564
565
// counts the number of crashes since the last clean start
566
_recentCrashes: 0,
567
568
// whether the last window was closed and should be restored
569
_restoreLastWindow: false,
570
571
// number of tabs currently restoring
572
_tabsRestoringCount: 0,
573
574
// When starting Firefox with a single private window, this is the place
575
// where we keep the session we actually wanted to restore in case the user
576
// decides to later open a non-private window as well.
577
_deferredInitialState: null,
578
579
// Keeps track of whether a notification needs to be sent that closed objects have changed.
580
_closedObjectsChanged: false,
581
582
// A promise resolved once initialization is complete
583
_deferredInitialized: (function() {
584
let deferred = {};
585
586
deferred.promise = new Promise((resolve, reject) => {
587
deferred.resolve = resolve;
588
deferred.reject = reject;
589
});
590
591
return deferred;
592
})(),
593
594
// Whether session has been initialized
595
_sessionInitialized: false,
596
597
// A promise resolved once all windows are restored.
598
_deferredAllWindowsRestored: (function() {
599
let deferred = {};
600
601
deferred.promise = new Promise((resolve, reject) => {
602
deferred.resolve = resolve;
603
deferred.reject = reject;
604
});
605
606
return deferred;
607
})(),
608
609
get promiseAllWindowsRestored() {
610
return this._deferredAllWindowsRestored.promise;
611
},
612
613
// Promise that is resolved when we're ready to initialize
614
// and restore the session.
615
_promiseReadyForInitialization: null,
616
617
// Keep busy state counters per window.
618
_windowBusyStates: new WeakMap(),
619
620
/**
621
* A promise fulfilled once initialization is complete.
622
*/
623
get promiseInitialized() {
624
return this._deferredInitialized.promise;
625
},
626
627
get canRestoreLastSession() {
628
return LastSession.canRestore;
629
},
630
631
set canRestoreLastSession(val) {
632
// Cheat a bit; only allow false.
633
if (!val) {
634
LastSession.clear();
635
}
636
},
637
638
/**
639
* Returns a string describing the last closed object, either "tab" or "window".
640
*
641
* This was added to support the sessions.restore WebExtensions API.
642
*/
643
get lastClosedObjectType() {
644
if (this._closedWindows.length) {
645
// Since there are closed windows, we need to check if there's a closed tab
646
// in one of the currently open windows that was closed after the
647
// last-closed window.
648
let tabTimestamps = [];
649
for (let window of Services.wm.getEnumerator("navigator:browser")) {
650
let windowState = this._windows[window.__SSi];
651
if (windowState && windowState._closedTabs[0]) {
652
tabTimestamps.push(windowState._closedTabs[0].closedAt);
653
}
654
}
655
if (
656
!tabTimestamps.length ||
657
tabTimestamps.sort((a, b) => b - a)[0] < this._closedWindows[0].closedAt
658
) {
659
return "window";
660
}
661
}
662
return "tab";
663
},
664
665
/**
666
* Returns a boolean that determines whether the session will be automatically
667
* restored upon the _next_ startup or a restart.
668
*/
669
get willAutoRestore() {
670
return (
671
!PrivateBrowsingUtils.permanentPrivateBrowsing &&
672
(Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
673
Services.prefs.getIntPref("browser.startup.page") ==
674
BROWSER_STARTUP_RESUME_SESSION)
675
);
676
},
677
678
/**
679
* Initialize the sessionstore service.
680
*/
681
init() {
682
if (this._initialized) {
683
throw new Error("SessionStore.init() must only be called once!");
684
}
685
686
TelemetryTimestamps.add("sessionRestoreInitialized");
687
OBSERVING.forEach(function(aTopic) {
688
Services.obs.addObserver(this, aTopic, true);
689
}, this);
690
691
this._initPrefs();
692
this._initialized = true;
693
694
Telemetry.getHistogramById("FX_SESSION_RESTORE_PRIVACY_LEVEL").add(
695
Services.prefs.getIntPref("browser.sessionstore.privacy_level")
696
);
697
},
698
699
/**
700
* Initialize the session using the state provided by SessionStartup
701
*/
702
initSession() {
703
TelemetryStopwatch.start("FX_SESSION_RESTORE_STARTUP_INIT_SESSION_MS");
704
let state;
705
let ss = SessionStartup;
706
707
if (ss.willRestore() || ss.sessionType == ss.DEFER_SESSION) {
708
state = ss.state;
709
}
710
711
if (state) {
712
try {
713
// If we're doing a DEFERRED session, then we want to pull pinned tabs
714
// out so they can be restored.
715
if (ss.sessionType == ss.DEFER_SESSION) {
716
let [iniState, remainingState] = this._prepDataForDeferredRestore(
717
state
718
);
719
// If we have a iniState with windows, that means that we have windows
720
// with app tabs to restore.
721
if (iniState.windows.length) {
722
// Move cookies over from the remaining state so that they're
723
// restored right away, and pinned tabs will load correctly.
724
iniState.cookies = remainingState.cookies;
725
delete remainingState.cookies;
726
state = iniState;
727
} else {
728
state = null;
729
}
730
731
if (remainingState.windows.length) {
732
LastSession.setState(remainingState);
733
}
734
} else {
735
// Get the last deferred session in case the user still wants to
736
// restore it
737
LastSession.setState(state.lastSessionState);
738
739
if (ss.willRestoreAsCrashed()) {
740
this._recentCrashes =
741
((state.session && state.session.recentCrashes) || 0) + 1;
742
743
if (this._needsRestorePage(state, this._recentCrashes)) {
744
// replace the crashed session with a restore-page-only session
745
let url = "about:sessionrestore";
746
let formdata = { id: { sessionData: state }, url };
747
let entry = {
748
url,
749
triggeringPrincipal_base64:
750
E10SUtils.SERIALIZED_SYSTEMPRINCIPAL,
751
};
752
state = { windows: [{ tabs: [{ entries: [entry], formdata }] }] };
753
} else if (
754
this._hasSingleTabWithURL(state.windows, "about:welcomeback")
755
) {
756
// On a single about:welcomeback URL that crashed, replace about:welcomeback
757
// with about:sessionrestore, to make clear to the user that we crashed.
758
state.windows[0].tabs[0].entries[0].url = "about:sessionrestore";
759
state.windows[0].tabs[0].entries[0].triggeringPrincipal_base64 =
760
E10SUtils.SERIALIZED_SYSTEMPRINCIPAL;
761
}
762
}
763
764
// Update the session start time using the restored session state.
765
this._updateSessionStartTime(state);
766
767
// Make sure that at least the first window doesn't have anything hidden.
768
delete state.windows[0].hidden;
769
// Since nothing is hidden in the first window, it cannot be a popup.
770
delete state.windows[0].isPopup;
771
// We don't want to minimize and then open a window at startup.
772
if (state.windows[0].sizemode == "minimized") {
773
state.windows[0].sizemode = "normal";
774
}
775
776
// clear any lastSessionWindowID attributes since those don't matter
777
// during normal restore
778
state.windows.forEach(function(aWindow) {
779
delete aWindow.__lastSessionWindowID;
780
});
781
}
782
} catch (ex) {
783
debug("The session file is invalid: " + ex);
784
}
785
}
786
787
// at this point, we've as good as resumed the session, so we can
788
// clear the resume_session_once flag, if it's set
789
if (
790
!RunState.isQuitting &&
791
this._prefBranch.getBoolPref("sessionstore.resume_session_once")
792
) {
793
this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
794
}
795
796
TelemetryStopwatch.finish("FX_SESSION_RESTORE_STARTUP_INIT_SESSION_MS");
797
return state;
798
},
799
800
_initPrefs() {
801
this._prefBranch = Services.prefs.getBranch("browser.");
802
803
gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
804
805
Services.prefs.addObserver("browser.sessionstore.debug", () => {
806
gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
807
});
808
809
this._max_tabs_undo = this._prefBranch.getIntPref(
810
"sessionstore.max_tabs_undo"
811
);
812
this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
813
814
this._max_windows_undo = this._prefBranch.getIntPref(
815
"sessionstore.max_windows_undo"
816
);
817
this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
818
819
this._restore_on_demand = this._prefBranch.getBoolPref(
820
"sessionstore.restore_on_demand"
821
);
822
this._prefBranch.addObserver("sessionstore.restore_on_demand", this, true);
823
824
gResistFingerprintingEnabled = Services.prefs.getBoolPref(
825
"privacy.resistFingerprinting"
826
);
827
Services.prefs.addObserver("privacy.resistFingerprinting", this);
828
},
829
830
/**
831
* Called on application shutdown, after notifications:
832
* quit-application-granted, quit-application
833
*/
834
_uninit: function ssi_uninit() {
835
if (!this._initialized) {
836
throw new Error("SessionStore is not initialized.");
837
}
838
839
// Prepare to close the session file and write the last state.
840
RunState.setClosing();
841
842
// save all data for session resuming
843
if (this._sessionInitialized) {
844
SessionSaver.run();
845
}
846
847
// clear out priority queue in case it's still holding refs
848
TabRestoreQueue.reset();
849
850
// Make sure to cancel pending saves.
851
SessionSaver.cancel();
852
},
853
854
/**
855
* Handle notifications
856
*/
857
observe: function ssi_observe(aSubject, aTopic, aData) {
858
switch (aTopic) {
859
case "browser-window-before-show": // catch new windows
860
this.onBeforeBrowserWindowShown(aSubject);
861
break;
862
case "domwindowclosed": // catch closed windows
863
this.onClose(aSubject).then(() => {
864
this._notifyOfClosedObjectsChange();
865
});
866
if (gDebuggingEnabled) {
867
Services.obs.notifyObservers(null, NOTIFY_DOMWINDOWCLOSED_HANDLED);
868
}
869
break;
870
case "quit-application-granted":
871
let syncShutdown = aData == "syncShutdown";
872
this.onQuitApplicationGranted(syncShutdown);
873
break;
874
case "browser-lastwindow-close-granted":
875
this.onLastWindowCloseGranted();
876
break;
877
case "quit-application":
878
this.onQuitApplication(aData);
879
break;
880
case "browser:purge-session-history": // catch sanitization
881
this.onPurgeSessionHistory();
882
this._notifyOfClosedObjectsChange();
883
break;
884
case "browser:purge-session-history-for-domain":
885
this.onPurgeDomainData(aData);
886
this._notifyOfClosedObjectsChange();
887
break;
888
case "nsPref:changed": // catch pref changes
889
this.onPrefChange(aData);
890
this._notifyOfClosedObjectsChange();
891
break;
892
case "idle-daily":
893
this.onIdleDaily();
894
this._notifyOfClosedObjectsChange();
895
break;
896
case "clear-origin-attributes-data":
897
let userContextId = 0;
898
try {
899
userContextId = JSON.parse(aData).userContextId;
900
} catch (e) {}
901
if (userContextId) {
902
this._forgetTabsWithUserContextId(userContextId);
903
}
904
break;
905
case "http-on-may-change-process":
906
this.onMayChangeProcess(aSubject);
907
break;
908
}
909
},
910
911
updateSessionStoreFromTablistener(aBrowser, aData) {
912
if (aBrowser.permanentKey == undefined) {
913
return;
914
}
915
916
// Ignore sessionStore update from previous epochs
917
if (!this.isCurrentEpoch(aBrowser, aData.epoch)) {
918
return;
919
}
920
921
TabState.update(aBrowser, aData);
922
let win = aBrowser.ownerGlobal;
923
this.saveStateDelayed(win);
924
925
if (aData.flushID) {
926
// This is an update kicked off by an async flush request. Notify the
927
// TabStateFlusher so that it can finish the request and notify its
928
// consumer that's waiting for the flush to be done.
929
TabStateFlusher.resolve(aBrowser, aData.flushID);
930
}
931
},
932
933
/**
934
* This method handles incoming messages sent by the session store content
935
* script via the Frame Message Manager or Parent Process Message Manager,
936
* and thus enables communication with OOP tabs.
937
*/
938
receiveMessage(aMessage) {
939
// If we got here, that means we're dealing with a frame message
940
// manager message, so the target will be a <xul:browser>.
941
var browser = aMessage.target;
942
let win = browser.ownerGlobal;
943
let tab = win ? win.gBrowser.getTabForBrowser(browser) : null;
944
945
// Ensure we receive only specific messages from <xul:browser>s that
946
// have no tab or window assigned, e.g. the ones that preload
947
// about:newtab pages, or windows that have closed.
948
if (!tab && !NOTAB_MESSAGES.has(aMessage.name)) {
949
throw new Error(
950
`received unexpected message '${aMessage.name}' ` +
951
`from a browser that has no tab or window`
952
);
953
}
954
955
let data = aMessage.data || {};
956
let hasEpoch = data.hasOwnProperty("epoch");
957
958
// Most messages sent by frame scripts require to pass an epoch.
959
if (!hasEpoch && !NOEPOCH_MESSAGES.has(aMessage.name)) {
960
throw new Error(`received message '${aMessage.name}' without an epoch`);
961
}
962
963
// Ignore messages from previous epochs.
964
if (hasEpoch && !this.isCurrentEpoch(browser, data.epoch)) {
965
return;
966
}
967
968
switch (aMessage.name) {
969
case "SessionStore:update":
970
// |browser.frameLoader| might be empty if the browser was already
971
// destroyed and its tab removed. In that case we still have the last
972
// frameLoader we know about to compare.
973
let frameLoader =
974
browser.frameLoader ||
975
this._lastKnownFrameLoader.get(browser.permanentKey);
976
977
// If the message isn't targeting the latest frameLoader discard it.
978
if (frameLoader != aMessage.targetFrameLoader) {
979
return;
980
}
981
982
if (aMessage.data.isFinal) {
983
// If this the final message we need to resolve all pending flush
984
// requests for the given browser as they might have been sent too
985
// late and will never respond. If they have been sent shortly after
986
// switching a browser's remoteness there isn't too much data to skip.
987
TabStateFlusher.resolveAll(browser);
988
} else if (aMessage.data.flushID) {
989
// This is an update kicked off by an async flush request. Notify the
990
// TabStateFlusher so that it can finish the request and notify its
991
// consumer that's waiting for the flush to be done.
992
TabStateFlusher.resolve(browser, aMessage.data.flushID);
993
}
994
995
// Ignore messages from <browser> elements that have crashed
996
// and not yet been revived.
997
if (this._crashedBrowsers.has(browser.permanentKey)) {
998
return;
999
}
1000
1001
// Update the tab's cached state.
1002
// Mark the window as dirty and trigger a delayed write.
1003
TabState.update(browser, aMessage.data);
1004
this.saveStateDelayed(win);
1005
1006
// Handle any updates sent by the child after the tab was closed. This
1007
// might be the final update as sent by the "unload" handler but also
1008
// any async update message that was sent before the child unloaded.
1009
if (this._closedTabs.has(browser.permanentKey)) {
1010
let { closedTabs, tabData } = this._closedTabs.get(
1011
browser.permanentKey
1012
);
1013
1014
// Update the closed tab's state. This will be reflected in its
1015
// window's list of closed tabs as that refers to the same object.
1016
TabState.copyFromCache(browser, tabData.state);
1017
1018
// Is this the tab's final message?
1019
if (aMessage.data.isFinal) {
1020
// We expect no further updates.
1021
this._closedTabs.delete(browser.permanentKey);
1022
// The tab state no longer needs this reference.
1023
delete tabData.permanentKey;
1024
1025
// Determine whether the tab state is worth saving.
1026
let shouldSave = this._shouldSaveTabState(tabData.state);
1027
let index = closedTabs.indexOf(tabData);
1028
1029
if (shouldSave && index == -1) {
1030
// If the tab state is worth saving and we didn't push it onto
1031
// the list of closed tabs when it was closed (because we deemed
1032
// the state not worth saving) then add it to the window's list
1033
// of closed tabs now.
1034
this.saveClosedTabData(closedTabs, tabData);
1035
} else if (!shouldSave && index > -1) {
1036
// Remove from the list of closed tabs. The update messages sent
1037
// after the tab was closed changed enough state so that we no
1038
// longer consider its data interesting enough to keep around.
1039
this.removeClosedTabData(closedTabs, index);
1040
}
1041
}
1042
}
1043
break;
1044
case "SessionStore:restoreHistoryComplete": {
1045
// Notify the tabbrowser that the tab chrome has been restored.
1046
let tabData = TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
1047
1048
// wall-paper fix for bug 439675: make sure that the URL to be loaded
1049
// is always visible in the address bar if no other value is present
1050
let activePageData = tabData.entries[tabData.index - 1] || null;
1051
let uri = activePageData ? activePageData.url || null : null;
1052
// NB: we won't set initial URIs (about:home, about:newtab, etc.) here
1053
// because their load will not normally trigger a location bar clearing
1054
// when they finish loading (to avoid race conditions where we then
1055
// clear user input instead), so we shouldn't set them here either.
1056
// They also don't fall under the issues in bug 439675 where user input
1057
// needs to be preserved if the load doesn't succeed.
1058
// We also don't do this for remoteness updates, where it should not
1059
// be necessary.
1060
if (
1061
!browser.userTypedValue &&
1062
uri &&
1063
!data.isRemotenessUpdate &&
1064
!win.gInitialPages.includes(uri)
1065
) {
1066
browser.userTypedValue = uri;
1067
}
1068
1069
// Update tab label and icon again after the tab history was updated.
1070
this.updateTabLabelAndIcon(tab, tabData);
1071
1072
let event = win.document.createEvent("Events");
1073
event.initEvent("SSTabRestoring", true, false);
1074
tab.dispatchEvent(event);
1075
break;
1076
}
1077
case "SessionStore:restoreTabContentStarted":
1078
if (TAB_STATE_FOR_BROWSER.get(browser) == TAB_STATE_NEEDS_RESTORE) {
1079
// If a load not initiated by sessionstore was started in a
1080
// previously pending tab. Mark the tab as no longer pending.
1081
this.markTabAsRestoring(tab);
1082
} else if (
1083
data.reason != RESTORE_TAB_CONTENT_REASON.NAVIGATE_AND_RESTORE
1084
) {
1085
// If the user was typing into the URL bar when we crashed, but hadn't hit
1086
// enter yet, then we just need to write that value to the URL bar without
1087
// loading anything. This must happen after the load, as the load will clear
1088
// userTypedValue.
1089
//
1090
// Note that we only want to do that if we're restoring state for reasons
1091
// _other_ than a navigateAndRestore remoteness-flip, as such a flip
1092
// implies that the user was navigating.
1093
let tabData = TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
1094
if (
1095
tabData.userTypedValue &&
1096
!tabData.userTypedClear &&
1097
!browser.userTypedValue
1098
) {
1099
browser.userTypedValue = tabData.userTypedValue;
1100
win.URLBarSetURI();
1101
}
1102
1103
// Remove state we don't need any longer.
1104
TabStateCache.update(browser, {
1105
userTypedValue: null,
1106
userTypedClear: null,
1107
});
1108
}
1109
break;
1110
case "SessionStore:restoreTabContentComplete":
1111
// This callback is used exclusively by tests that want to
1112
// monitor the progress of network loads.
1113
if (gDebuggingEnabled) {
1114
Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED);
1115
}
1116
1117
SessionStoreInternal._resetLocalTabRestoringState(tab);
1118
SessionStoreInternal.restoreNextTab();
1119
1120
this._sendTabRestoredNotification(tab, data.isRemotenessUpdate);
1121
1122
Services.obs.notifyObservers(
1123
null,
1124
"sessionstore-one-or-no-tab-restored"
1125
);
1126
break;
1127
case "SessionStore:crashedTabRevived":
1128
// The browser was revived by navigating to a different page
1129
// manually, so we remove it from the ignored browser set.
1130
this._crashedBrowsers.delete(browser.permanentKey);
1131
break;
1132
case "SessionStore:error":
1133
TabStateFlusher.resolveAll(
1134
browser,
1135
false,
1136
"Received error from the content process"
1137
);
1138
break;
1139
default:
1140
throw new Error(`received unknown message '${aMessage.name}'`);
1141
}
1142
},
1143
1144
/* ........ Window Event Handlers .............. */
1145
1146
/**
1147
* Implement EventListener for handling various window and tab events
1148
*/
1149
handleEvent: function ssi_handleEvent(aEvent) {
1150
let win = aEvent.currentTarget.ownerGlobal;
1151
let target = aEvent.originalTarget;
1152
switch (aEvent.type) {
1153
case "TabOpen":
1154
this.onTabAdd(win);
1155
break;
1156
case "TabBrowserInserted":
1157
this.onTabBrowserInserted(win, target);
1158
break;
1159
case "TabClose":
1160
// `adoptedBy` will be set if the tab was closed because it is being
1161
// moved to a new window.
1162
if (!aEvent.detail.adoptedBy) {
1163
this.onTabClose(win, target);
1164
}
1165
this.onTabRemove(win, target);
1166
this._notifyOfClosedObjectsChange();
1167
break;
1168
case "TabSelect":
1169
this.onTabSelect(win);
1170
break;
1171
case "TabShow":
1172
this.onTabShow(win, target);
1173
break;
1174
case "TabHide":
1175
this.onTabHide(win, target);
1176
break;
1177
case "TabPinned":
1178
case "TabUnpinned":
1179
case "SwapDocShells":
1180
this.saveStateDelayed(win);
1181
break;
1182
case "oop-browser-crashed":
1183
if (aEvent.isTopFrame) {
1184
this.onBrowserCrashed(target);
1185
}
1186
break;
1187
case "XULFrameLoaderCreated":
1188
if (
1189
target.namespaceURI == NS_XUL &&
1190
target.localName == "browser" &&
1191
target.frameLoader &&
1192
target.permanentKey
1193
) {
1194
this._lastKnownFrameLoader.set(
1195
target.permanentKey,
1196
target.frameLoader
1197
);
1198
this.resetEpoch(target);
1199
}
1200
break;
1201
case "BrowserChangedProcess":
1202
let newEpoch =
1203
1 +
1204
Math.max(
1205
this.getCurrentEpoch(target),
1206
this.getCurrentEpoch(aEvent.otherBrowser)
1207
);
1208
this.setCurrentEpoch(target, newEpoch);
1209
target.messageManager.sendAsyncMessage(
1210
"SessionStore:becomeActiveProcess",
1211
{
1212
epoch: newEpoch,
1213
}
1214
);
1215
break;
1216
default:
1217
throw new Error(`unhandled event ${aEvent.type}?`);
1218
}
1219
this._clearRestoringWindows();
1220
},
1221
1222
/**
1223
* Generate a unique window identifier
1224
* @return string
1225
* A unique string to identify a window
1226
*/
1227
_generateWindowID: function ssi_generateWindowID() {
1228
return "window" + this._nextWindowID++;
1229
},
1230
1231
/**
1232
* Registers and tracks a given window.
1233
*
1234
* @param aWindow
1235
* Window reference
1236
*/
1237
onLoad(aWindow) {
1238
// return if window has already been initialized
1239
if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi]) {
1240
return;
1241
}
1242
1243
// ignore windows opened while shutting down
1244
if (RunState.isQuitting) {
1245
return;
1246
}
1247
1248
// Assign the window a unique identifier we can use to reference
1249
// internal data about the window.
1250
aWindow.__SSi = this._generateWindowID();
1251
1252
let mm = aWindow.getGroupMessageManager("browsers");
1253
MESSAGES.forEach(msg => {
1254
let listenWhenClosed = CLOSED_MESSAGES.has(msg);
1255
mm.addMessageListener(msg, this, listenWhenClosed);
1256
});
1257
1258
// Load the frame script after registering listeners.
1259
mm.loadFrameScript(
1261
true,
1262
true
1263
);
1264
1265
// and create its data object
1266
this._windows[aWindow.__SSi] = {
1267
tabs: [],
1268
selected: 0,
1269
_closedTabs: [],
1270
busy: false,
1271
};
1272
1273
if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) {
1274
this._windows[aWindow.__SSi].isPrivate = true;
1275
}
1276
if (!this._isWindowLoaded(aWindow)) {
1277
this._windows[aWindow.__SSi]._restoring = true;
1278
}
1279
if (!aWindow.toolbar.visible) {
1280
this._windows[aWindow.__SSi].isPopup = true;
1281
}
1282
1283
let tabbrowser = aWindow.gBrowser;
1284
1285
// add tab change listeners to all already existing tabs
1286
for (let i = 0; i < tabbrowser.tabs.length; i++) {
1287
this.onTabBrowserInserted(aWindow, tabbrowser.tabs[i]);
1288
}
1289
// notification of tab add/remove/selection/show/hide
1290
TAB_EVENTS.forEach(function(aEvent) {
1291
tabbrowser.tabContainer.addEventListener(aEvent, this, true);
1292
}, this);
1293
1294
// Keep track of a browser's latest frameLoader.
1295
aWindow.gBrowser.addEventListener("XULFrameLoaderCreated", this);
1296
aWindow.gBrowser.addEventListener("BrowserChangedProcess", this);
1297
},
1298
1299
/**
1300
* Initializes a given window.
1301
*
1302
* Windows are registered as soon as they are created but we need to wait for
1303
* the session file to load, and the initial window's delayed startup to
1304
* finish before initializing a window, i.e. restoring data into it.
1305
*
1306
* @param aWindow
1307
* Window reference
1308
* @param aInitialState
1309
* The initial state to be loaded after startup (optional)
1310
*/
1311
initializeWindow(aWindow, aInitialState = null) {
1312
let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
1313
1314
// perform additional initialization when the first window is loading
1315
if (RunState.isStopped) {
1316
RunState.setRunning();
1317
1318
// restore a crashed session resp. resume the last session if requested
1319
if (aInitialState) {
1320
// Don't write to disk right after startup. Set the last time we wrote
1321
// to disk to NOW() to enforce a full interval before the next write.
1322
SessionSaver.updateLastSaveTime();
1323
1324
if (isPrivateWindow) {
1325
// We're starting with a single private window. Save the state we
1326
// actually wanted to restore so that we can do it later in case
1327
// the user opens another, non-private window.
1328
this._deferredInitialState = SessionStartup.state;
1329
1330
// Nothing to restore now, notify observers things are complete.
1331
Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED);
1332
Services.obs.notifyObservers(
1333
null,
1334
"sessionstore-one-or-no-tab-restored"
1335
);
1336
this._deferredAllWindowsRestored.resolve();
1337
} else {
1338
TelemetryTimestamps.add("sessionRestoreRestoring");
1339
this._restoreCount = aInitialState.windows
1340
? aInitialState.windows.length
1341
: 0;
1342
1343
// global data must be restored before restoreWindow is called so that
1344
// it happens before observers are notified
1345
this._globalState.setFromState(aInitialState);
1346
1347
// Restore session cookies before loading any tabs.
1348
SessionCookies.restore(aInitialState.cookies || []);
1349
1350
let overwrite = this._isCmdLineEmpty(aWindow, aInitialState);
1351
let options = { firstWindow: true, overwriteTabs: overwrite };
1352
this.restoreWindows(aWindow, aInitialState, options);
1353
}
1354
} else {
1355
// Nothing to restore, notify observers things are complete.
1356
Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED);
1357
Services.obs.notifyObservers(
1358
null,
1359
"sessionstore-one-or-no-tab-restored"
1360
);
1361
this._deferredAllWindowsRestored.resolve();
1362
}
1363
// this window was opened by _openWindowWithState
1364
} else if (!this._isWindowLoaded(aWindow)) {
1365
// We want to restore windows after all windows have opened (since bug
1366
// 1034036), so bail out here.
1367
return;
1368
// The user opened another, non-private window after starting up with
1369
// a single private one. Let's restore the session we actually wanted to
1370
// restore at startup.
1371
} else if (
1372
this._deferredInitialState &&
1373
!isPrivateWindow &&
1374
aWindow.toolbar.visible
1375
) {
1376
// global data must be restored before restoreWindow is called so that
1377
// it happens before observers are notified
1378
this._globalState.setFromState(this._deferredInitialState);
1379
1380
this._restoreCount = this._deferredInitialState.windows
1381
? this._deferredInitialState.windows.length
1382
: 0;
1383
this.restoreWindows(aWindow, this._deferredInitialState, {
1384
firstWindow: true,
1385
});
1386
this._deferredInitialState = null;
1387
} else if (
1388
this._restoreLastWindow &&
1389
aWindow.toolbar.visible &&
1390
this._closedWindows.length &&
1391
!isPrivateWindow
1392
) {
1393
// default to the most-recently closed window
1394
// don't use popup windows
1395
let closedWindowState = null;
1396
let closedWindowIndex;
1397
for (let i = 0; i < this._closedWindows.length; i++) {
1398
// Take the first non-popup, point our object at it, and break out.
1399
if (!this._closedWindows[i].isPopup) {
1400
closedWindowState = this._closedWindows[i];
1401
closedWindowIndex = i;
1402
break;
1403
}
1404
}
1405
1406
if (closedWindowState) {
1407
let newWindowState;
1408
if (
1409
AppConstants.platform == "macosx" ||
1410
!SessionStartup.willRestore()
1411
) {
1412
// We want to split the window up into pinned tabs and unpinned tabs.
1413
// Pinned tabs should be restored. If there are any remaining tabs,
1414
// they should be added back to _closedWindows.
1415
// We'll cheat a little bit and reuse _prepDataForDeferredRestore
1416
// even though it wasn't built exactly for this.
1417
let [
1418
appTabsState,
1419
normalTabsState,
1420
] = this._prepDataForDeferredRestore({
1421
windows: [closedWindowState],
1422
});
1423
1424
// These are our pinned tabs, which we should restore
1425
if (appTabsState.windows.length) {
1426
newWindowState = appTabsState.windows[0];
1427
delete newWindowState.__lastSessionWindowID;
1428
}
1429
1430
// In case there were no unpinned tabs, remove the window from _closedWindows
1431
if (!normalTabsState.windows.length) {
1432
this._removeClosedWindow(closedWindowIndex);
1433
// Or update _closedWindows with the modified state
1434
} else {
1435
delete normalTabsState.windows[0].__lastSessionWindowID;
1436
this._closedWindows[closedWindowIndex] = normalTabsState.windows[0];
1437
}
1438
} else {
1439
// If we're just restoring the window, make sure it gets removed from
1440
// _closedWindows.
1441
this._removeClosedWindow(closedWindowIndex);
1442
newWindowState = closedWindowState;
1443
delete newWindowState.hidden;
1444
}
1445
1446
if (newWindowState) {
1447
// Ensure that the window state isn't hidden
1448
this._restoreCount = 1;
1449
let state = { windows: [newWindowState] };
1450
let options = { overwriteTabs: this._isCmdLineEmpty(aWindow, state) };
1451
this.restoreWindow(aWindow, newWindowState, options);
1452
}
1453
}
1454
// we actually restored the session just now.
1455
this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
1456
}
1457
if (this._restoreLastWindow && aWindow.toolbar.visible) {
1458
// always reset (if not a popup window)
1459
// we don't want to restore a window directly after, for example,
1460
// undoCloseWindow was executed.
1461
this._restoreLastWindow = false;
1462
}
1463
},
1464
1465
/**
1466
* Called right before a new browser window is shown.
1467
* @param aWindow
1468
* Window reference
1469
*/
1470
onBeforeBrowserWindowShown(aWindow) {
1471
// Register the window.
1472
this.onLoad(aWindow);
1473
1474
// Some are waiting for this window to be shown, which is now, so let's resolve
1475
// the deferred operation.
1476
let deferred = WINDOW_SHOWING_PROMISES.get(aWindow);
1477
if (deferred) {
1478
deferred.resolve(aWindow);
1479
WINDOW_SHOWING_PROMISES.delete(aWindow);
1480
}
1481
1482
// Just call initializeWindow() directly if we're initialized already.
1483
if (this._sessionInitialized) {
1484
this.initializeWindow(aWindow);
1485
return;
1486
}
1487
1488
// The very first window that is opened creates a promise that is then
1489
// re-used by all subsequent windows. The promise will be used to tell
1490
// when we're ready for initialization.
1491
if (!this._promiseReadyForInitialization) {
1492
// Wait for the given window's delayed startup to be finished.
1493
let promise = new Promise(resolve => {
1494
Services.obs.addObserver(function obs(subject, topic) {
1495
if (aWindow == subject) {
1496
Services.obs.removeObserver(obs, topic);
1497
resolve();
1498
}
1499
}, "browser-delayed-startup-finished");
1500
});
1501
1502
// We are ready for initialization as soon as the session file has been
1503
// read from disk and the initial window's delayed startup has finished.
1504
this._promiseReadyForInitialization = Promise.all([
1505
promise,
1506
SessionStartup.onceInitialized,
1507
]);
1508
}
1509
1510
// We can't call this.onLoad since initialization
1511
// hasn't completed, so we'll wait until it is done.
1512
// Even if additional windows are opened and wait
1513
// for initialization as well, the first opened
1514
// window should execute first, and this.onLoad
1515
// will be called with the initialState.
1516
this._promiseReadyForInitialization
1517
.then(() => {
1518
if (aWindow.closed) {
1519
return;
1520
}
1521
1522
if (this._sessionInitialized) {
1523
this.initializeWindow(aWindow);
1524
} else {
1525
let initialState = this.initSession();
1526
this._sessionInitialized = true;
1527
1528
if (initialState) {
1529
Services.obs.notifyObservers(null, NOTIFY_RESTORING_ON_STARTUP);
1530
}
1531
TelemetryStopwatch.start(
1532
"FX_SESSION_RESTORE_STARTUP_ONLOAD_INITIAL_WINDOW_MS"
1533
);
1534
this.initializeWindow(aWindow, initialState);
1535
TelemetryStopwatch.finish(
1536
"FX_SESSION_RESTORE_STARTUP_ONLOAD_INITIAL_WINDOW_MS"
1537
);
1538
1539
// Let everyone know we're done.
1540
this._deferredInitialized.resolve();
1541
}
1542
})
1543
.catch(console.error);
1544
},
1545
1546
/**
1547
* On window close...
1548
* - remove event listeners from tabs
1549
* - save all window data
1550
* @param aWindow
1551
* Window reference
1552
*
1553
* @returns a Promise
1554
*/
1555
onClose: function ssi_onClose(aWindow) {
1556
let completionPromise = Promise.resolve();
1557
// this window was about to be restored - conserve its original data, if any
1558
let isFullyLoaded = this._isWindowLoaded(aWindow);
1559
if (!isFullyLoaded) {
1560
if (!aWindow.__SSi) {
1561
aWindow.__SSi = this._generateWindowID();
1562
}
1563
1564
let restoreID = WINDOW_RESTORE_IDS.get(aWindow);
1565
this._windows[aWindow.__SSi] = this._statesToRestore[
1566
restoreID
1567
].windows[0];
1568
delete this._statesToRestore[restoreID];
1569
WINDOW_RESTORE_IDS.delete(aWindow);
1570
}
1571
1572
// ignore windows not tracked by SessionStore
1573
if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
1574
return completionPromise;
1575
}
1576
1577
// notify that the session store will stop tracking this window so that
1578
// extensions can store any data about this window in session store before
1579
// that's not possible anymore
1580
let event = aWindow.document.createEvent("Events");
1581
event.initEvent("SSWindowClosing", true, false);
1582
aWindow.dispatchEvent(event);
1583
1584
if (this.windowToFocus && this.windowToFocus == aWindow) {
1585
delete this.windowToFocus;
1586
}
1587
1588
var tabbrowser = aWindow.gBrowser;
1589
1590
let browsers = Array.from(tabbrowser.browsers);
1591
1592
TAB_EVENTS.forEach(function(aEvent) {
1593
tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
1594
}, this);
1595
1596
aWindow.gBrowser.removeEventListener("XULFrameLoaderCreated", this);
1597
aWindow.gBrowser.removeEventListener("BrowserChangedProcess", this);
1598
1599
let winData = this._windows[aWindow.__SSi];
1600
1601
// Collect window data only when *not* closed during shutdown.
1602
if (RunState.isRunning) {
1603
// Grab the most recent window data. The tab data will be updated
1604
// once we finish flushing all of the messages from the tabs.
1605
let tabMap = this._collectWindowData(aWindow);
1606
1607
for (let [tab, tabData] of tabMap) {
1608
let permanentKey = tab.linkedBrowser.permanentKey;
1609
this._closedWindowTabs.set(permanentKey, tabData);
1610
}
1611
1612
if (isFullyLoaded) {
1613
winData.title =
1614
tabbrowser.selectedBrowser.contentTitle ||
1615
tabbrowser.selectedTab.label;
1616
}
1617
1618
if (AppConstants.platform != "macosx") {
1619
// Until we decide otherwise elsewhere, this window is part of a series
1620
// of closing windows to quit.
1621
winData._shouldRestore = true;
1622
}
1623
1624
// Store the window's close date to figure out when each individual tab
1625
// was closed. This timestamp should allow re-arranging data based on how
1626
// recently something was closed.
1627
winData.closedAt = Date.now();
1628
1629
// we don't want to save the busy state
1630
delete winData.busy;
1631
1632
// When closing windows one after the other until Firefox quits, we
1633
// will move those closed in series back to the "open windows" bucket
1634
// before writing to disk. If however there is only a single window
1635
// with tabs we deem not worth saving then we might end up with a
1636
// random closed or even a pop-up window re-opened. To prevent that
1637
// we explicitly allow saving an "empty" window state.
1638
let isLastWindow =
1639
Object.keys(this._windows).length == 1 &&
1640
!this._closedWindows.some(win => win._shouldRestore || false);
1641
1642
// clear this window from the list, since it has definitely been closed.
1643
delete this._windows[aWindow.__SSi];
1644
1645
// This window has the potential to be saved in the _closedWindows
1646
// array (maybeSaveClosedWindows gets the final call on that).
1647
this._saveableClosedWindowData.add(winData);
1648
1649
// Now we have to figure out if this window is worth saving in the _closedWindows
1650
// Object.
1651
//
1652
// We're about to flush the tabs from this window, but it's possible that we
1653
// might never hear back from the content process(es) in time before the user
1654
// chooses to restore the closed window. So we do the following:
1655
//
1656
// 1) Use the tab state cache to determine synchronously if the window is
1657
// worth stashing in _closedWindows.
1658
// 2) Flush the window.
1659
// 3) When the flush is complete, revisit our decision to store the window
1660
// in _closedWindows, and add/remove as necessary.
1661
if (!winData.isPrivate) {
1662
// Remove any open private tabs the window may contain.
1663
PrivacyFilter.filterPrivateTabs(winData);
1664
this.maybeSaveClosedWindow(winData, isLastWindow);
1665
}
1666
1667
completionPromise = TabStateFlusher.flushWindow(aWindow).then(() => {
1668
// At this point, aWindow is closed! You should probably not try to
1669
// access any DOM elements from aWindow within this callback unless
1670
// you're holding on to them in the closure.
1671
1672
for (let browser of browsers) {
1673
if (this._closedWindowTabs.has(browser.permanentKey)) {
1674
let tabData = this._closedWindowTabs.get(browser.permanentKey);
1675
TabState.copyFromCache(browser, tabData);
1676
this._closedWindowTabs.delete(browser.permanentKey);
1677
}
1678
}
1679
1680
// Save non-private windows if they have at
1681
// least one saveable tab or are the last window.
1682
if (!winData.isPrivate) {
1683
// It's possible that a tab switched its privacy state at some point
1684
// before our flush, so we need to filter again.
1685
PrivacyFilter.filterPrivateTabs(winData);
1686
this.maybeSaveClosedWindow(winData, isLastWindow);
1687
}
1688
1689
// Update the tabs data now that we've got the most
1690
// recent information.
1691
this.cleanUpWindow(aWindow, winData, browsers);
1692
1693
// save the state without this window to disk
1694
this.saveStateDelayed();
1695
});
1696
} else {
1697
this.cleanUpWindow(aWindow, winData, browsers);
1698
}
1699
1700
for (let i = 0; i < tabbrowser.tabs.length; i++) {
1701
this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
1702
}
1703
1704
return completionPromise;
1705
},
1706
1707
/**
1708
* Clean up the message listeners on a window that has finally
1709
* gone away. Call this once you're sure you don't want to hear
1710
* from any of this windows tabs from here forward.
1711
*
1712
* @param aWindow
1713
* The browser window we're cleaning up.
1714
* @param winData
1715
* The data for the window that we should hold in the
1716
* DyingWindowCache in case anybody is still holding a
1717
* reference to it.
1718
*/
1719
cleanUpWindow(aWindow, winData, browsers) {
1720
// Any leftover TabStateFlusher Promises need to be resolved now,
1721
// since we're about to remove the message listeners.
1722
for (let browser of browsers) {
1723
TabStateFlusher.resolveAll(browser);
1724
}
1725
1726
// Cache the window state until it is completely gone.
1727
DyingWindowCache.set(aWindow, winData);
1728
1729
let mm = aWindow.getGroupMessageManager("browsers");
1730
MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
1731
1732
this._saveableClosedWindowData.delete(winData);
1733
delete aWindow.__SSi;
1734
},
1735
1736
/**
1737
* Decides whether or not a closed window should be put into the
1738
* _closedWindows Object. This might be called multiple times per
1739
* window, and will do the right thing of moving the window data
1740
* in or out of _closedWindows if the winData indicates that our
1741
* need for saving it has changed.
1742
*
1743
* @param winData
1744
* The data for the closed window that we might save.
1745
* @param isLastWindow
1746
* Whether or not the window being closed is the last
1747
* browser window. Callers of this function should pass
1748
* in the value of SessionStoreInternal.atLastWindow for
1749
* this argument, and pass in the same value if they happen
1750
* to call this method again asynchronously (for example, after
1751
* a window flush).
1752
*/
1753
maybeSaveClosedWindow(winData, isLastWindow) {
1754
// Make sure SessionStore is still running, and make sure that we
1755
// haven't chosen to forget this window.
1756
if (RunState.isRunning && this._saveableClosedWindowData.has(winData)) {
1757
// Determine whether the window has any tabs worth saving.
1758
let hasSaveableTabs = winData.tabs.some(this._shouldSaveTabState);
1759
1760
// Note that we might already have this window stored in
1761
// _closedWindows from a previous call to this function.
1762
let winIndex = this._closedWindows.indexOf(winData);
1763
let alreadyStored = winIndex != -1;
1764
let shouldStore = hasSaveableTabs || isLastWindow;
1765
1766
if (shouldStore && !alreadyStored) {
1767
let index = this._closedWindows.findIndex(win => {
1768
return win.closedAt < winData.closedAt;
1769
});
1770
1771
// If we found no tab closed before our
1772
// tab then just append it to the list.
1773
if (index == -1) {
1774
index = this._closedWindows.length;
1775
}
1776
1777
// About to save the closed window, add a unique ID.
1778
winData.closedId = this._nextClosedId++;
1779
1780
// Insert tabData at the right position.
1781
this._closedWindows.splice(index, 0, winData);
1782
this._capClosedWindows();
1783
this._closedObjectsChanged = true;
1784
} else if (!shouldStore && alreadyStored) {
1785
this._removeClosedWindow(winIndex);
1786
}
1787
}
1788
},
1789
1790
/**
1791
* On quit application granted
1792
*/
1793
onQuitApplicationGranted: function ssi_onQuitApplicationGranted(
1794
syncShutdown = false
1795
) {
1796
// Collect an initial snapshot of window data before we do the flush.
1797
let index = 0;
1798
for (let window of this._orderedBrowserWindows) {
1799
this._collectWindowData(window);
1800
this._windows[window.__SSi].zIndex = ++index;
1801
}
1802
1803
// Now add an AsyncShutdown blocker that'll spin the event loop
1804
// until the windows have all been flushed.
1805
1806
// This progress object will track the state of async window flushing
1807
// and will help us debug things that go wrong with our AsyncShutdown
1808
// blocker.
1809
let progress = { total: -1, current: -1 };
1810
1811
// We're going down! Switch state so that we treat closing windows and
1812
// tabs correctly.
1813
RunState.setQuitting();
1814
1815
if (!syncShutdown) {
1816
// We've got some time to shut down, so let's do this properly that there
1817
// will be a complete session available upon next startup.
1818
// To prevent a blocker from taking longer than the DELAY_CRASH_MS limit
1819
// (which will cause a crash) of AsyncShutdown whilst flushing all windows,
1820
// we resolve the Promise blocker once:
1821
// 1. the flush duration exceeds 10 seconds before DELAY_CRASH_MS, or
1822
// 2. 'oop-frameloader-crashed', or
1823
// 3. 'ipc:content-shutdown' is observed.
1824
AsyncShutdown.quitApplicationGranted.addBlocker(
1825
"SessionStore: flushing all windows",
1826
() => {
1827
// Set up the list of promises that will signal a complete sessionstore
1828
// shutdown: either all data is saved, or we crashed or the message IPC
1829
// channel went away in the meantime.
1830
let promises = [this.flushAllWindowsAsync(progress)];
1831
1832
const observeTopic = topic => {
1833
let deferred = PromiseUtils.defer();
1834
const cleanup = () => {
1835
try {
1836
Services.obs.removeObserver(deferred.resolve, topic);
1837
} catch (ex) {
1838
Cu.reportError(
1839
"SessionStore: exception whilst flushing all windows: " + ex
1840
);
1841
}
1842
};
1843
Services.obs.addObserver(subject => {
1844
// Skip abort on ipc:content-shutdown if not abnormal/crashed
1845
subject.QueryInterface(Ci.nsIPropertyBag2);
1846
if (
1847
!(topic == "ipc:content-shutdown" && !subject.get("abnormal"))
1848
) {
1849
deferred.resolve();
1850
}
1851
}, topic);
1852
deferred.promise.then(cleanup, cleanup);
1853
return deferred;
1854
};
1855
1856
// Build a list of deferred executions that require cleanup once the
1857
// Promise race is won.
1858
// Ensure that the timer fires earlier than the AsyncShutdown crash timer.
1859
let waitTimeMaxMs = Math.max(0, AsyncShutdown.DELAY_CRASH_MS - 10000);
1860
let defers = [
1861
this.looseTimer(waitTimeMaxMs),
1862
observeTopic("oop-frameloader-crashed"),
1863
observeTopic("ipc:content-shutdown"),
1864
];
1865
// Add these monitors to the list of Promises to start the race.
1866
promises.push(...defers.map(deferred => deferred.promise));
1867
1868
return Promise.race(promises).then(() => {
1869
// When a Promise won the race, make sure we clean up the running
1870
// monitors.
1871
defers.forEach(deferred => deferred.reject());
1872
});
1873
},
1874
() => progress
1875
);
1876
} else {
1877
// We have to shut down NOW, which means we only get to save whatever
1878
// we already had cached.
1879
}
1880
},
1881
1882
/**
1883
* An async Task that iterates all open browser windows and flushes
1884
* any outstanding messages from their tabs. This will also close
1885
* all of the currently open windows while we wait for the flushes
1886
* to complete.
1887
*
1888
* @param progress (Object)
1889
* Optional progress object that will be updated as async
1890
* window flushing progresses. flushAllWindowsSync will
1891
* write to the following properties:
1892
*
1893
* total (int):
1894
* The total number of windows to be flushed.
1895
* current (int):
1896
* The current window that we're waiting for a flush on.
1897
*
1898
* @return Promise
1899
*/
1900
async flushAllWindowsAsync(progress = {}) {
1901
let windowPromises = new Map();
1902
// We collect flush promises and close each window immediately so that
1903
// the user can't start changing any window state while we're waiting
1904
// for the flushes to finish.
1905
for (let window of this._browserWindows) {
1906
windowPromises.set(window, TabStateFlusher.flushWindow(window));
1907
1908
// We have to wait for these messages to come up from
1909
// each window and each browser. In the meantime, hide
1910
// the windows to improve perceived shutdown speed.
1911
let baseWin = window.docShell.treeOwner.QueryInterface(Ci.nsIBaseWindow);
1912
baseWin.visibility = false;
1913
}
1914
1915
progress.total = windowPromises.size;
1916
progress.current = 0;
1917
1918
// We'll iterate through the Promise array, yielding each one, so as to
1919
// provide useful progress information to AsyncShutdown.
1920
for (let [win, promise] of windowPromises) {
1921
await promise;
1922
this._collectWindowData(win);
1923
progress.current++;
1924
}
1925
1926
// We must cache this because _getTopWindow will always
1927
// return null by the time quit-application occurs.
1928
var activeWindow = this._getTopWindow();
1929
if (activeWindow) {
1930
this.activeWindowSSiCache = activeWindow.__SSi || "";
1931
}
1932
DirtyWindows.clear();
1933
},
1934
1935
/**
1936
* On last browser window close
1937
*/
1938
onLastWindowCloseGranted: function ssi_onLastWindowCloseGranted() {
1939
// last browser window is quitting.
1940
// remember to restore the last window when another browser window is opened
1941
// do not account for pref(resume_session_once) at this point, as it might be
1942
// set by another observer getting this notice after us
1943
this._restoreLastWindow = true;
1944
},
1945
1946
/**
1947
* On quitting application
1948
* @param aData
1949
* String type of quitting
1950
*/
1951
onQuitApplication: function ssi_onQuitApplication(aData) {
1952
if (aData == "restart" || aData == "os-restart") {
1953
if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
1954
if (
1955
aData == "os-restart" &&
1956
!this._prefBranch.getBoolPref("sessionstore.resume_session_once")
1957
) {
1958
this._prefBranch.setBoolPref(
1959
"sessionstore.resuming_after_os_restart",
1960
true
1961
);
1962
}
1963
this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
1964
}
1965
1966
// The browser:purge-session-history notification fires after the
1967
// quit-application notification so unregister the
1968
// browser:purge-session-history notification to prevent clearing
1969
// session data on disk on a restart. It is also unnecessary to
1970
// perform any other sanitization processing on a restart as the
1971
// browser is about to exit anyway.
1972
Services.obs.removeObserver(this, "browser:purge-session-history");
1973
}
1974
1975
if (aData != "restart") {
1976
// Throw away the previous session on shutdown without notification
1977
LastSession.clear(true);
1978
}
1979
1980
this._uninit();
1981
},
1982
1983
/**
1984
* On purge of session history
1985
*/
1986
onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
1987
SessionFile.wipe();
1988
// If the browser is shutting down, simply return after clearing the
1989
// session data on disk as this notification fires after the