PreferencesBackupResource.sys.mjs (7952B)
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 { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; 6 import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs"; 7 8 const lazy = {}; 9 10 ChromeUtils.defineESModuleGetters(lazy, { 11 ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs", 12 SearchUtils: "moz-src:///toolkit/components/search/SearchUtils.sys.mjs", 13 }); 14 15 const PROFILE_RESTORATION_DATE_PREF = "browser.backup.profile-restoration-date"; 16 17 /** 18 * Class representing files that modify preferences and permissions within a user profile. 19 */ 20 export class PreferencesBackupResource extends BackupResource { 21 static get key() { 22 return "preferences"; 23 } 24 25 static get requiresEncryption() { 26 return false; 27 } 28 29 /** 30 * Adds prefs to the override map that are currently set but should not be 31 * included in the backup. Override them with null values to prevent 32 * serialization. 33 * 34 * @param {nsIPrefOverrideMap} prefsOverrideMap 35 * @returns {nsIPrefOverrideMap} prefsOverrideMap with ignored prefs added 36 */ 37 static addPrefsToIgnoreInBackup(prefsOverrideMap) { 38 // List of prefs we never backup. 39 let kIgnoredPrefs = [ 40 "app.normandy.user_id", 41 "toolkit.telemetry.cachedClientID", 42 "toolkit.telemetry.cachedProfileGroupID", 43 PROFILE_RESTORATION_DATE_PREF, 44 ]; 45 46 const backupPrefs = Services.prefs.getChildList("browser.backup."); 47 kIgnoredPrefs = kIgnoredPrefs.concat(backupPrefs); 48 49 // Prefs with this prefix are always overriden. 50 const kNimbusMetadataPrefPrefix = "nimbus."; 51 52 for (const pref of kIgnoredPrefs) { 53 if (Services.prefs.getPrefType(pref) !== Services.prefs.PREF_INVALID) { 54 prefsOverrideMap.addEntry(pref, null); 55 } 56 } 57 58 const nimbusPrefs = Services.prefs.getChildList(kNimbusMetadataPrefPrefix); 59 for (const pref of nimbusPrefs) { 60 prefsOverrideMap.addEntry(pref, null); 61 } 62 63 return prefsOverrideMap; 64 } 65 66 async backup( 67 stagingPath, 68 profilePath = PathUtils.profileDir, 69 _isEncrypting = false 70 ) { 71 // These are files that can be simply copied into the staging folder using 72 // IOUtils.copy. 73 const simpleCopyFiles = [ 74 "xulstore.json", 75 "containers.json", 76 "handlers.json", 77 "search.json.mozlz4", 78 "user.js", 79 "chrome", 80 ]; 81 await BackupResource.copyFiles(profilePath, stagingPath, simpleCopyFiles); 82 83 // prefs.js is a special case - we have a helper function to flush the 84 // current prefs state to disk off of the main thread. 85 let prefsDestPath = PathUtils.join(stagingPath, "prefs.js"); 86 let prefsDestFile = await IOUtils.getFile(prefsDestPath); 87 await lazy.ExperimentAPI._rsLoader.withUpdateLock(async () => { 88 await Services.prefs.backupPrefFile( 89 prefsDestFile, 90 PreferencesBackupResource.addPrefsToIgnoreInBackup( 91 lazy.ExperimentAPI.manager.store.getOriginalPrefValuesForAllActiveEnrollments() 92 ) 93 ); 94 }); 95 96 // During recovery, we need to recompute verification hashes for any 97 // custom engines, but only for engines that were originally passing 98 // verification. We'll store the profile path at backup time in our 99 // ManifestEntry so that we can do that verification check at recover-time. 100 return { profilePath }; 101 } 102 103 async recover(manifestEntry, recoveryPath, destProfilePath) { 104 const SEARCH_PREF_FILENAME = "search.json.mozlz4"; 105 const RECOVERY_SEARCH_PREF_PATH = PathUtils.join( 106 recoveryPath, 107 SEARCH_PREF_FILENAME 108 ); 109 110 if (await IOUtils.exists(RECOVERY_SEARCH_PREF_PATH)) { 111 // search.json.mozlz4 may contain hash values that need to be recomputed 112 // now that the profile directory has changed. 113 let searchPrefs = await IOUtils.readJSON(RECOVERY_SEARCH_PREF_PATH, { 114 decompress: true, 115 }); 116 117 // ... but we only want to do this for engines that had valid verification 118 // hashes for the original profile path. 119 const ORIGINAL_PROFILE_PATH = manifestEntry.profilePath; 120 121 if (ORIGINAL_PROFILE_PATH) { 122 searchPrefs.engines = searchPrefs.engines.map(engine => { 123 if (engine._metaData.loadPathHash) { 124 let loadPath = engine._loadPath; 125 if ( 126 engine._metaData.loadPathHash == 127 lazy.SearchUtils.getVerificationHash( 128 loadPath, 129 ORIGINAL_PROFILE_PATH 130 ) 131 ) { 132 engine._metaData.loadPathHash = 133 lazy.SearchUtils.getVerificationHash(loadPath, destProfilePath); 134 } 135 } 136 return engine; 137 }); 138 139 if ( 140 searchPrefs.metaData.defaultEngineIdHash && 141 searchPrefs.metaData.defaultEngineIdHash == 142 lazy.SearchUtils.getVerificationHash( 143 searchPrefs.metaData.defaultEngineId, 144 ORIGINAL_PROFILE_PATH 145 ) 146 ) { 147 searchPrefs.metaData.defaultEngineIdHash = 148 lazy.SearchUtils.getVerificationHash( 149 searchPrefs.metaData.defaultEngineId, 150 destProfilePath 151 ); 152 } 153 154 if ( 155 searchPrefs.metaData.privateDefaultEngineIdHash && 156 searchPrefs.metaData.privateDefaultEngineIdHash == 157 lazy.SearchUtils.getVerificationHash( 158 searchPrefs.metaData.privateDefaultEngineId, 159 ORIGINAL_PROFILE_PATH 160 ) 161 ) { 162 searchPrefs.metaData.privateDefaultEngineIdHash = 163 lazy.SearchUtils.getVerificationHash( 164 searchPrefs.metaData.privateDefaultEngineId, 165 destProfilePath 166 ); 167 } 168 } 169 170 await IOUtils.writeJSON( 171 PathUtils.join(destProfilePath, SEARCH_PREF_FILENAME), 172 searchPrefs, 173 { compress: true } 174 ); 175 } 176 177 const simpleCopyFiles = [ 178 "prefs.js", 179 "xulstore.json", 180 "containers.json", 181 "handlers.json", 182 "user.js", 183 "chrome", 184 ]; 185 await BackupResource.copyFiles( 186 recoveryPath, 187 destProfilePath, 188 simpleCopyFiles 189 ); 190 191 // Append browser.backup.scheduled.last-backup-file to prefs.js with the 192 // current timestamp. 193 const LINEBREAK = AppConstants.platform === "win" ? "\r\n" : "\n"; 194 let prefsFile = await IOUtils.getFile(destProfilePath); 195 prefsFile.append("prefs.js"); 196 // We should always have recovered a prefs.js but, if we didn't for any 197 // reason, we can still write the timestamp. Since we are creating the 198 // prefs.js file, we need to add the preamble. 199 const includePreamble = !(await IOUtils.exists(prefsFile.path)); 200 let addToPrefsJs = includePreamble ? Services.prefs.prefsJsPreamble : ""; 201 addToPrefsJs += `user_pref("${PROFILE_RESTORATION_DATE_PREF}", ${Math.round(Date.now() / 1000)});${LINEBREAK}`; 202 await IOUtils.writeUTF8(prefsFile.path, addToPrefsJs, { 203 mode: "appendOrCreate", 204 }); 205 return null; 206 } 207 208 async measure(profilePath = PathUtils.profileDir) { 209 const files = [ 210 "prefs.js", 211 "xulstore.json", 212 "containers.json", 213 "handlers.json", 214 "search.json.mozlz4", 215 "user.js", 216 ]; 217 let fullSize = 0; 218 219 for (let filePath of files) { 220 let resourcePath = PathUtils.join(profilePath, filePath); 221 let resourceSize = await BackupResource.getFileSize(resourcePath); 222 if (Number.isInteger(resourceSize)) { 223 fullSize += resourceSize; 224 } 225 } 226 227 const chromeDirectoryPath = PathUtils.join(profilePath, "chrome"); 228 let chromeDirectorySize = 229 await BackupResource.getDirectorySize(chromeDirectoryPath); 230 if (Number.isInteger(chromeDirectorySize)) { 231 fullSize += chromeDirectorySize; 232 } 233 234 Glean.browserBackup.preferencesSize.set(fullSize); 235 } 236 }