SessionStoreBackupResource.sys.mjs (4998B)
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 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 6 7 import { 8 BackupResource, 9 bytesToFuzzyKilobytes, 10 } from "resource:///modules/backup/BackupResource.sys.mjs"; 11 12 const lazy = {}; 13 14 ChromeUtils.defineESModuleGetters(lazy, { 15 BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", 16 SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", 17 setTimeout: "resource://gre/modules/Timer.sys.mjs", 18 TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs", 19 }); 20 21 ChromeUtils.defineLazyGetter(lazy, "logConsole", function () { 22 return console.createInstance({ 23 prefix: "SessionStoreBackupResource", 24 maxLogLevel: Services.prefs.getBoolPref("browser.backup.log", false) 25 ? "Debug" 26 : "Warn", 27 }); 28 }); 29 30 XPCOMUtils.defineLazyPreferenceGetter( 31 lazy, 32 "TAB_FLUSH_TIMEOUT", 33 "browser.backup.tab-flush-timeout", 34 5000 35 ); 36 37 /** 38 * Class representing Session store related files within a user profile. 39 */ 40 export class SessionStoreBackupResource extends BackupResource { 41 // Allow creator to provide a "SessionStore" object, so we can use mocks in 42 // testing. Passing `null` means use the real service. 43 constructor(sessionStore = null) { 44 super(); 45 this._sessionStore = sessionStore; 46 } 47 48 static get key() { 49 return "sessionstore"; 50 } 51 52 static get requiresEncryption() { 53 // Session store data does not require encryption, but if encryption is 54 // disabled, then session cookies will be cleared from the backup before 55 // writing it to the disk. 56 return false; 57 } 58 59 get #sessionStore() { 60 return this._sessionStore || lazy.SessionStore; 61 } 62 63 get filteredSessionStoreState() { 64 let sessionStoreState = this.#sessionStore.getCurrentState(true); 65 // Preserving session cookies in a backup used on a different machine 66 // may break behavior for websites. So we leave them out of the backup. 67 sessionStoreState.cookies = []; 68 69 // Remove session storage. 70 if (sessionStoreState.windows) { 71 // We don't want to backup private windows 72 sessionStoreState.windows = sessionStoreState.windows.filter( 73 w => !w?.isPrivate 74 ); 75 sessionStoreState.windows.forEach(win => { 76 if (win.tabs) { 77 win.tabs.forEach(tab => delete tab.storage); 78 } 79 if (win._closedTabs) { 80 win._closedTabs.forEach(closedTab => delete closedTab.state.storage); 81 } 82 }); 83 } 84 if (sessionStoreState.savedGroups) { 85 sessionStoreState.savedGroups.forEach(group => { 86 if (group.tabs) { 87 group.tabs.forEach(tab => delete tab.state.storage); 88 } 89 }); 90 } 91 92 return sessionStoreState; 93 } 94 95 async backup( 96 stagingPath, 97 profilePath = PathUtils.profileDir, 98 _isEncrypting = false 99 ) { 100 // Flush tab state so backups receive the correct url to restore. 101 await Promise.race([ 102 Promise.allSettled( 103 lazy.BrowserWindowTracker.orderedWindows.map( 104 lazy.TabStateFlusher.flushWindow 105 ) 106 ), 107 new Promise((_, reject) => 108 lazy.setTimeout(reject, lazy.TAB_FLUSH_TIMEOUT, { timeout: true }) 109 ), 110 ]).catch(e => { 111 if (e?.timeout) { 112 lazy.logConsole.warn("Timed out waiting while flushing tab state."); 113 } else { 114 lazy.logConsole.error( 115 "Unrecognized error while flushing tab state.", 116 e 117 ); 118 } 119 }); 120 121 let sessionStorePath = PathUtils.join(stagingPath, "sessionstore.jsonlz4"); 122 123 await IOUtils.writeJSON(sessionStorePath, this.filteredSessionStoreState, { 124 compress: true, 125 }); 126 await BackupResource.copyFiles(profilePath, stagingPath, [ 127 "sessionstore-backups", 128 ]); 129 130 return null; 131 } 132 133 async recover(_manifestEntry, recoveryPath, destProfilePath) { 134 await BackupResource.copyFiles(recoveryPath, destProfilePath, [ 135 "sessionstore.jsonlz4", 136 "sessionstore-backups", 137 ]); 138 139 return null; 140 } 141 142 async measure(profilePath = PathUtils.profileDir) { 143 // Get the current state of the session store JSON and 144 // measure it's uncompressed size. 145 let sessionStoreJson = this.#sessionStore.getCurrentState(true); 146 let sessionStoreSize = new TextEncoder().encode( 147 JSON.stringify(sessionStoreJson) 148 ).byteLength; 149 let sessionStoreNearestTenKb = bytesToFuzzyKilobytes(sessionStoreSize); 150 151 Glean.browserBackup.sessionStoreSize.set(sessionStoreNearestTenKb); 152 153 let sessionStoreBackupsDirectoryPath = PathUtils.join( 154 profilePath, 155 "sessionstore-backups" 156 ); 157 let sessionStoreBackupsDirectorySize = 158 await BackupResource.getDirectorySize(sessionStoreBackupsDirectoryPath); 159 160 Glean.browserBackup.sessionStoreBackupsDirectorySize.set( 161 sessionStoreBackupsDirectorySize 162 ); 163 } 164 }