MiscDataBackupResource.sys.mjs (5265B)
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 { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs"; 6 7 const lazy = {}; 8 ChromeUtils.defineESModuleGetters(lazy, { 9 ASRouterStorage: "resource:///modules/asrouter/ASRouterStorage.sys.mjs", 10 ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs", 11 }); 12 13 const SNIPPETS_TABLE_NAME = "snippets"; 14 const FILES_FOR_BACKUP = [ 15 "enumerate_devices.txt", 16 "protections.sqlite", 17 "SiteSecurityServiceState.bin", 18 ]; 19 20 /** 21 * Class representing miscellaneous files for telemetry, site storage, 22 * media device origin mapping, chrome privileged IndexedDB databases, 23 * and Mozilla Accounts within a user profile. 24 */ 25 export class MiscDataBackupResource extends BackupResource { 26 static get key() { 27 return "miscellaneous"; 28 } 29 30 static get requiresEncryption() { 31 return false; 32 } 33 34 async backup( 35 stagingPath, 36 profilePath = PathUtils.profileDir, 37 _isEncrypting = false 38 ) { 39 const files = ["enumerate_devices.txt", "SiteSecurityServiceState.bin"]; 40 await BackupResource.copyFiles(profilePath, stagingPath, files); 41 42 const sqliteDatabases = ["protections.sqlite"]; 43 await BackupResource.copySqliteDatabases( 44 profilePath, 45 stagingPath, 46 sqliteDatabases 47 ); 48 49 // Bug 1890585 - we don't currently have the ability to copy the 50 // chrome-privileged IndexedDB databases under storage/permanent/chrome. 51 // Instead, we'll manually export any IndexedDB data we need to backup 52 // to a separate JSON file. 53 54 // The first IndexedDB database we want to back up is the ASRouter 55 // one - specifically, the "snippets" table, as this contains information 56 // on ASRouter impressions, blocked messages, message group impressions, 57 // etc. 58 let storage = new lazy.ASRouterStorage({ 59 storeNames: [SNIPPETS_TABLE_NAME], 60 }); 61 let snippetsTable = await storage.getDbTable(SNIPPETS_TABLE_NAME); 62 let snippetsObj = {}; 63 for (let key of await snippetsTable.getAllKeys()) { 64 snippetsObj[key] = await snippetsTable.get(key); 65 } 66 let snippetsBackupFile = PathUtils.join( 67 stagingPath, 68 "activity-stream-snippets.json" 69 ); 70 await IOUtils.writeJSON(snippetsBackupFile, snippetsObj); 71 72 return null; 73 } 74 75 async recover(_manifestEntry, recoveryPath, destProfilePath) { 76 await BackupResource.copyFiles( 77 recoveryPath, 78 destProfilePath, 79 FILES_FOR_BACKUP 80 ); 81 82 // The times.json file, the one that powers ProfileAge, works hand in hand 83 // with the Telemetry client ID. We don't want to accidentally _overwrite_ 84 // a pre-existing times.json with data from a different profile, because 85 // then the client ID wouldn't match the times.json data anymore. 86 // 87 // The rule that we're following for backups and recoveries is that the 88 // recovered profile always inherits the client ID (and therefore the 89 // times.json) from the profile that _initiated recovery_. 90 // 91 // This means we want to copy the times.json file from the profile that's 92 // currently in use to the destProfilePath. 93 await BackupResource.copyFiles(PathUtils.profileDir, destProfilePath, [ 94 "times.json", 95 ]); 96 97 // We also want to write the recoveredFromBackup timestamp now. 98 let profileAge = await lazy.ProfileAge(destProfilePath); 99 await profileAge.recordRecoveredFromBackup(); 100 101 // The activity-stream-snippets data will need to be written during the 102 // postRecovery phase, so we'll stash the path to the JSON file in the 103 // post recovery entry. 104 let snippetsBackupFile = PathUtils.join( 105 recoveryPath, 106 "activity-stream-snippets.json" 107 ); 108 return { snippetsBackupFile }; 109 } 110 111 async postRecovery(postRecoveryEntry) { 112 let { snippetsBackupFile } = postRecoveryEntry; 113 114 // If for some reason, the activity-stream-snippets data file has been 115 // removed already, there's nothing to do. 116 if (!IOUtils.exists(snippetsBackupFile)) { 117 return; 118 } 119 120 let snippetsData = await IOUtils.readJSON(snippetsBackupFile); 121 let storage = new lazy.ASRouterStorage({ 122 storeNames: [SNIPPETS_TABLE_NAME], 123 }); 124 let snippetsTable = await storage.getDbTable(SNIPPETS_TABLE_NAME); 125 for (let key in snippetsData) { 126 let value = snippetsData[key]; 127 await snippetsTable.set(key, value); 128 } 129 } 130 131 async measure(profilePath = PathUtils.profileDir) { 132 let fullSize = 0; 133 134 for (let filePath of FILES_FOR_BACKUP) { 135 let resourcePath = PathUtils.join(profilePath, filePath); 136 let resourceSize = await BackupResource.getFileSize(resourcePath); 137 if (Number.isInteger(resourceSize)) { 138 fullSize += resourceSize; 139 } 140 } 141 142 let chromeIndexedDBDirPath = PathUtils.join( 143 profilePath, 144 "storage", 145 "permanent", 146 "chrome" 147 ); 148 let chromeIndexedDBDirSize = await BackupResource.getDirectorySize( 149 chromeIndexedDBDirPath 150 ); 151 if (Number.isInteger(chromeIndexedDBDirSize)) { 152 fullSize += chromeIndexedDBDirSize; 153 } 154 155 Glean.browserBackup.miscDataSize.set(fullSize); 156 } 157 }