test_AddonsBackupResource.js (12922B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { AddonsBackupResource } = ChromeUtils.importESModule( 7 "resource:///modules/backup/AddonsBackupResource.sys.mjs" 8 ); 9 10 /** 11 * Tests that we can measure the size of all the addons & extensions data. 12 */ 13 add_task(async function test_measure() { 14 Services.fog.testResetFOG(); 15 Services.telemetry.clearScalars(); 16 17 const EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON = 250; 18 const EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE = 500; 19 const EXPECTED_KILOBYTES_FOR_STORAGE_SYNC = 50; 20 const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_A = 600; 21 const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_B = 400; 22 const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C = 150; 23 const EXPECTED_KILOBYTES_FOR_EXTENSIONS_DIRECTORY = 1000; 24 const EXPECTED_KILOBYTES_FOR_EXTENSION_DATA = 100; 25 const EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE = 200; 26 27 let tempDir = PathUtils.tempDir; 28 29 // Create extensions json files (all the same size). 30 const extensionsFilePath = PathUtils.join(tempDir, "extensions.json"); 31 await createKilobyteSizedFile( 32 extensionsFilePath, 33 EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON 34 ); 35 const extensionSettingsFilePath = PathUtils.join( 36 tempDir, 37 "extension-settings.json" 38 ); 39 await createKilobyteSizedFile( 40 extensionSettingsFilePath, 41 EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON 42 ); 43 const extensionsPrefsFilePath = PathUtils.join( 44 tempDir, 45 "extension-preferences.json" 46 ); 47 await createKilobyteSizedFile( 48 extensionsPrefsFilePath, 49 EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON 50 ); 51 const addonStartupFilePath = PathUtils.join(tempDir, "addonStartup.json.lz4"); 52 await createKilobyteSizedFile( 53 addonStartupFilePath, 54 EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON 55 ); 56 57 // Create the extension store permissions data file. 58 let extensionStorePermissionsDataSize = PathUtils.join( 59 tempDir, 60 "extension-store-permissions", 61 "data.safe.bin" 62 ); 63 await createKilobyteSizedFile( 64 extensionStorePermissionsDataSize, 65 EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE 66 ); 67 68 // Create the storage sync database file. 69 let storageSyncPath = PathUtils.join(tempDir, "storage-sync-v2.sqlite"); 70 await createKilobyteSizedFile( 71 storageSyncPath, 72 EXPECTED_KILOBYTES_FOR_STORAGE_SYNC 73 ); 74 75 // Create the extensions directory with XPI files. 76 let extensionsXPIAPath = PathUtils.join( 77 tempDir, 78 "extensions", 79 "extension-b.xpi" 80 ); 81 let extensionsXPIBPath = PathUtils.join( 82 tempDir, 83 "extensions", 84 "extension-a.xpi" 85 ); 86 await createKilobyteSizedFile( 87 extensionsXPIAPath, 88 EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_A 89 ); 90 await createKilobyteSizedFile( 91 extensionsXPIBPath, 92 EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_B 93 ); 94 // Should be ignored. 95 let extensionsXPIStagedPath = PathUtils.join( 96 tempDir, 97 "extensions", 98 "staged", 99 "staged-test-extension.xpi" 100 ); 101 let extensionsXPITrashPath = PathUtils.join( 102 tempDir, 103 "extensions", 104 "trash", 105 "trashed-test-extension.xpi" 106 ); 107 let extensionsXPIUnpackedPath = PathUtils.join( 108 tempDir, 109 "extensions", 110 "unpacked-extension.xpi", 111 "manifest.json" 112 ); 113 await createKilobyteSizedFile( 114 extensionsXPIStagedPath, 115 EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C 116 ); 117 await createKilobyteSizedFile( 118 extensionsXPITrashPath, 119 EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C 120 ); 121 await createKilobyteSizedFile( 122 extensionsXPIUnpackedPath, 123 EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C 124 ); 125 126 // Create the browser extension data directory. 127 let browserExtensionDataPath = PathUtils.join( 128 tempDir, 129 "browser-extension-data", 130 "test-file" 131 ); 132 await createKilobyteSizedFile( 133 browserExtensionDataPath, 134 EXPECTED_KILOBYTES_FOR_EXTENSION_DATA 135 ); 136 137 // Create the extensions storage directory. 138 let extensionsStoragePath = PathUtils.join( 139 tempDir, 140 "storage", 141 "default", 142 "moz-extension+++test-extension-id", 143 "idb", 144 "data.sqlite" 145 ); 146 // Other storage files that should not be counted. 147 let otherStoragePath = PathUtils.join( 148 tempDir, 149 "storage", 150 "default", 151 "https+++accounts.firefox.com", 152 "ls", 153 "data.sqlite" 154 ); 155 156 await createKilobyteSizedFile( 157 extensionsStoragePath, 158 EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE 159 ); 160 await createKilobyteSizedFile( 161 otherStoragePath, 162 EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE 163 ); 164 165 // Measure all the extensions data. 166 let extensionsBackupResource = new AddonsBackupResource(); 167 await extensionsBackupResource.measure(tempDir); 168 169 let extensionsJsonSizeMeasurement = 170 Glean.browserBackup.extensionsJsonSize.testGetValue(); 171 Assert.equal( 172 extensionsJsonSizeMeasurement, 173 EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON * 4, // There are 4 equally sized files. 174 "Should have collected the correct measurement of the total size of all extensions JSON files" 175 ); 176 177 let extensionStorePermissionsDataSizeMeasurement = 178 Glean.browserBackup.extensionStorePermissionsDataSize.testGetValue(); 179 Assert.equal( 180 extensionStorePermissionsDataSizeMeasurement, 181 EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE, 182 "Should have collected the correct measurement of the size of the extension store permissions data" 183 ); 184 185 let storageSyncSizeMeasurement = 186 Glean.browserBackup.storageSyncSize.testGetValue(); 187 Assert.equal( 188 storageSyncSizeMeasurement, 189 EXPECTED_KILOBYTES_FOR_STORAGE_SYNC, 190 "Should have collected the correct measurement of the size of the storage sync database" 191 ); 192 193 let extensionsXPIDirectorySizeMeasurement = 194 Glean.browserBackup.extensionsXpiDirectorySize.testGetValue(); 195 Assert.equal( 196 extensionsXPIDirectorySizeMeasurement, 197 EXPECTED_KILOBYTES_FOR_EXTENSIONS_DIRECTORY, 198 "Should have collected the correct measurement of the size 2 equally sized XPI files in the extensions directory" 199 ); 200 201 let browserExtensionDataSizeMeasurement = 202 Glean.browserBackup.browserExtensionDataSize.testGetValue(); 203 Assert.equal( 204 browserExtensionDataSizeMeasurement, 205 EXPECTED_KILOBYTES_FOR_EXTENSION_DATA, 206 "Should have collected the correct measurement of the size of the browser extension data directory" 207 ); 208 209 let extensionsStorageSizeMeasurement = 210 Glean.browserBackup.extensionsStorageSize.testGetValue(); 211 Assert.equal( 212 extensionsStorageSizeMeasurement, 213 EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE, 214 "Should have collected the correct measurement of all the extensions storage" 215 ); 216 217 // Compare glean vs telemetry measurements 218 let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false); 219 TelemetryTestUtils.assertScalar( 220 scalars, 221 "browser.backup.extensions_json_size", 222 extensionsJsonSizeMeasurement, 223 "Glean and telemetry measurements for extensions JSON should be equal" 224 ); 225 TelemetryTestUtils.assertScalar( 226 scalars, 227 "browser.backup.extension_store_permissions_data_size", 228 extensionStorePermissionsDataSizeMeasurement, 229 "Glean and telemetry measurements for extension store permissions data should be equal" 230 ); 231 TelemetryTestUtils.assertScalar( 232 scalars, 233 "browser.backup.storage_sync_size", 234 storageSyncSizeMeasurement, 235 "Glean and telemetry measurements for storage sync database should be equal" 236 ); 237 TelemetryTestUtils.assertScalar( 238 scalars, 239 "browser.backup.extensions_xpi_directory_size", 240 extensionsXPIDirectorySizeMeasurement, 241 "Glean and telemetry measurements for extensions directory should be equal" 242 ); 243 TelemetryTestUtils.assertScalar( 244 scalars, 245 "browser.backup.browser_extension_data_size", 246 browserExtensionDataSizeMeasurement, 247 "Glean and telemetry measurements for browser extension data should be equal" 248 ); 249 TelemetryTestUtils.assertScalar( 250 scalars, 251 "browser.backup.extensions_storage_size", 252 extensionsStorageSizeMeasurement, 253 "Glean and telemetry measurements for extensions storage should be equal" 254 ); 255 256 await maybeRemovePath(tempDir); 257 }); 258 259 /** 260 * Tests that we can handle the extension store permissions data 261 * and moz-extension IndexedDB databases not existing. 262 */ 263 add_task(async function test_measure_missing_data() { 264 Services.fog.testResetFOG(); 265 266 let tempDir = PathUtils.tempDir; 267 268 let extensionsBackupResource = new AddonsBackupResource(); 269 await extensionsBackupResource.measure(tempDir); 270 271 let extensionStorePermissionsDataSizeMeasurement = 272 Glean.browserBackup.extensionStorePermissionsDataSize.testGetValue(); 273 Assert.equal( 274 extensionStorePermissionsDataSizeMeasurement, 275 null, 276 "Should NOT have collected a measurement for the missing permissions data" 277 ); 278 279 let extensionsStorageSizeMeasurement = 280 Glean.browserBackup.extensionsStorageSize.testGetValue(); 281 Assert.equal( 282 extensionsStorageSizeMeasurement, 283 null, 284 "Should NOT have collected a measurement for the missing storage data" 285 ); 286 }); 287 288 /** 289 * Test that the backup method correctly copies items from the profile directory 290 * into the staging directory. 291 */ 292 add_task(async function test_backup() { 293 let sandbox = sinon.createSandbox(); 294 295 let addonsBackupResource = new AddonsBackupResource(); 296 let sourcePath = await IOUtils.createUniqueDirectory( 297 PathUtils.tempDir, 298 "AddonsBackupResource-source-test" 299 ); 300 let stagingPath = await IOUtils.createUniqueDirectory( 301 PathUtils.tempDir, 302 "AddonsBackupResource-staging-test" 303 ); 304 305 const simpleCopyFiles = [ 306 { path: "extensions.json" }, 307 { path: "extension-settings.json" }, 308 { path: "extension-preferences.json" }, 309 { path: "addonStartup.json.lz4" }, 310 { 311 path: [ 312 "browser-extension-data", 313 "{11aa1234-f111-1234-abcd-a9b8c7654d32}", 314 ], 315 }, 316 { path: ["extension-store-permissions", "data.safe.bin"] }, 317 { path: ["extensions", "{11aa1234-f111-1234-abcd-a9b8c7654d32}.xpi"] }, 318 ]; 319 await createTestFiles(sourcePath, simpleCopyFiles); 320 321 const junkFiles = [{ path: ["extensions", "junk"] }]; 322 await createTestFiles(sourcePath, junkFiles); 323 324 // Create a fake storage-sync-v2 database file. We don't expect this to 325 // be copied to the staging directory in this test due to our stubbing 326 // of the backup method, so we don't include it in `simpleCopyFiles`. 327 await createTestFiles(sourcePath, [{ path: "storage-sync-v2.sqlite" }]); 328 329 let fakeConnection = { 330 backup: sandbox.stub().resolves(true), 331 close: sandbox.stub().resolves(true), 332 }; 333 sandbox.stub(Sqlite, "openConnection").returns(fakeConnection); 334 335 let manifestEntry = await addonsBackupResource.backup( 336 stagingPath, 337 sourcePath 338 ); 339 Assert.equal( 340 manifestEntry, 341 null, 342 "AddonsBackupResource.backup should return null as its ManifestEntry" 343 ); 344 345 await assertFilesExist(stagingPath, simpleCopyFiles); 346 347 let junkFile = PathUtils.join(stagingPath, "extensions", "junk"); 348 Assert.equal( 349 await IOUtils.exists(junkFile), 350 false, 351 `${junkFile} should not exist in the staging folder` 352 ); 353 354 // Make sure storage-sync-v2 database is backed up. 355 Assert.ok( 356 fakeConnection.backup.calledOnce, 357 "Called backup the expected number of times for all connections" 358 ); 359 Assert.ok( 360 fakeConnection.backup.calledWith( 361 PathUtils.join(stagingPath, "storage-sync-v2.sqlite") 362 ), 363 "Called backup on the storage-sync-v2 Sqlite connection" 364 ); 365 366 await maybeRemovePath(stagingPath); 367 await maybeRemovePath(sourcePath); 368 369 sandbox.restore(); 370 }); 371 372 /** 373 * Test that the recover method correctly copies items from the recovery 374 * directory into the destination profile directory. 375 */ 376 add_task(async function test_recover() { 377 let addonsBackupResource = new AddonsBackupResource(); 378 let recoveryPath = await IOUtils.createUniqueDirectory( 379 PathUtils.tempDir, 380 "addonsBackupResource-recovery-test" 381 ); 382 let destProfilePath = await IOUtils.createUniqueDirectory( 383 PathUtils.tempDir, 384 "addonsBackupResource-test-profile" 385 ); 386 387 const files = [ 388 { path: "extensions.json" }, 389 { path: "extension-settings.json" }, 390 { path: "extension-preferences.json" }, 391 { path: "addonStartup.json.lz4" }, 392 { path: "storage-sync-v2.sqlite" }, 393 { path: ["browser-extension-data", "addon@darkreader.org.xpi", "data"] }, 394 { path: ["extensions", "addon@darkreader.org.xpi"] }, 395 { path: ["extension-store-permissions", "data.safe.bin"] }, 396 ]; 397 await createTestFiles(recoveryPath, files); 398 399 // The backup method is expected to have returned a null ManifestEntry 400 let postRecoveryEntry = await addonsBackupResource.recover( 401 null /* manifestEntry */, 402 recoveryPath, 403 destProfilePath 404 ); 405 Assert.equal( 406 postRecoveryEntry, 407 null, 408 "AddonsBackupResource.recover should return null as its post " + 409 "recovery entry" 410 ); 411 412 await assertFilesExist(destProfilePath, files); 413 414 await maybeRemovePath(recoveryPath); 415 await maybeRemovePath(destProfilePath); 416 });