browser_sync_chooseWhatToSync.js (12703B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { Service } = ChromeUtils.importESModule( 7 "resource://services-sync/service.sys.mjs" 8 ); 9 const { UIState } = ChromeUtils.importESModule( 10 "resource://services-sync/UIState.sys.mjs" 11 ); 12 13 // This obj will be used in both tests 14 // First test makes sure accepting the preferences matches these values 15 // Second test makes sure the cancel dialog STILL matches these values 16 const syncPrefs = { 17 "services.sync.engine.addons": false, 18 "services.sync.engine.bookmarks": true, 19 "services.sync.engine.history": true, 20 "services.sync.engine.tabs": false, 21 "services.sync.engine.prefs": false, 22 "services.sync.engine.passwords": false, 23 "services.sync.engine.addresses": false, 24 "services.sync.engine.creditcards": false, 25 }; 26 27 add_setup(async () => { 28 UIState._internal.notifyStateUpdated = () => {}; 29 const origNotifyStateUpdated = UIState._internal.notifyStateUpdated; 30 const origGet = UIState.get; 31 UIState.get = () => { 32 return { status: UIState.STATUS_SIGNED_IN, email: "foo@bar.com" }; 33 }; 34 35 registerCleanupFunction(() => { 36 UIState._internal.notifyStateUpdated = origNotifyStateUpdated; 37 UIState.get = origGet; 38 }); 39 }); 40 41 /** 42 * We don't actually enable sync here, but we just check that the preferences are correct 43 * when the callback gets hit (accepting/cancelling the dialog) 44 * See https://bugzilla.mozilla.org/show_bug.cgi?id=1584132. 45 */ 46 47 add_task(async function testDialogAccept() { 48 await SpecialPowers.pushPrefEnv({ 49 set: [["identity.fxaccounts.enabled", true]], 50 }); 51 52 await openPreferencesViaOpenPreferencesAPI("paneGeneral", { 53 leaveOpen: true, 54 }); 55 56 // This will check if the callback was actually called during the test 57 let callbackCalled = false; 58 59 // Enabling all the sync UI is painful in tests, so we just open the dialog manually 60 let syncWindow = await openAndLoadSubDialog( 61 "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml", 62 null, 63 {}, 64 () => { 65 for (const [prefKey, prefValue] of Object.entries(syncPrefs)) { 66 Assert.equal( 67 Services.prefs.getBoolPref(prefKey), 68 prefValue, 69 `${prefValue} is expected value` 70 ); 71 } 72 callbackCalled = true; 73 } 74 ); 75 76 Assert.ok(syncWindow, "Choose what to sync window opened"); 77 let syncChooseDialog = 78 syncWindow.document.getElementById("syncChooseOptions"); 79 let syncCheckboxes = syncChooseDialog.querySelectorAll( 80 "checkbox[preference]" 81 ); 82 83 // Adjust the checkbox values to the expectedValues in the list 84 [...syncCheckboxes].forEach(checkbox => { 85 if (syncPrefs[checkbox.getAttribute("preference")] !== checkbox.checked) { 86 checkbox.click(); 87 } 88 }); 89 90 syncChooseDialog.acceptDialog(); 91 BrowserTestUtils.removeTab(gBrowser.selectedTab); 92 Assert.ok(callbackCalled, "Accept callback was called"); 93 }); 94 95 add_task(async function testDialogCancel() { 96 const cancelSyncPrefs = { 97 "services.sync.engine.addons": true, 98 "services.sync.engine.bookmarks": false, 99 "services.sync.engine.history": true, 100 "services.sync.engine.tabs": true, 101 "services.sync.engine.prefs": false, 102 "services.sync.engine.passwords": true, 103 "services.sync.engine.addresses": true, 104 "services.sync.engine.creditcards": false, 105 }; 106 107 await SpecialPowers.pushPrefEnv({ 108 set: [["identity.fxaccounts.enabled", true]], 109 }); 110 111 await openPreferencesViaOpenPreferencesAPI("paneGeneral", { 112 leaveOpen: true, 113 }); 114 115 // This will check if the callback was actually called during the test 116 let callbackCalled = false; 117 118 // Enabling all the sync UI is painful in tests, so we just open the dialog manually 119 let syncWindow = await openAndLoadSubDialog( 120 "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml", 121 null, 122 {}, 123 () => { 124 // We want to test against our previously accepted values in the last test 125 for (const [prefKey, prefValue] of Object.entries(syncPrefs)) { 126 Assert.equal( 127 Services.prefs.getBoolPref(prefKey), 128 prefValue, 129 `${prefValue} is expected value` 130 ); 131 } 132 callbackCalled = true; 133 } 134 ); 135 136 ok(syncWindow, "Choose what to sync window opened"); 137 let syncChooseDialog = 138 syncWindow.document.getElementById("syncChooseOptions"); 139 let syncCheckboxes = syncChooseDialog.querySelectorAll( 140 "checkbox[preference]" 141 ); 142 143 // This time we're adjusting to the cancel list 144 [...syncCheckboxes].forEach(checkbox => { 145 if ( 146 cancelSyncPrefs[checkbox.getAttribute("preference")] !== checkbox.checked 147 ) { 148 checkbox.click(); 149 } 150 }); 151 152 syncChooseDialog.cancelDialog(); 153 BrowserTestUtils.removeTab(gBrowser.selectedTab); 154 Assert.ok(callbackCalled, "Cancel callback was called"); 155 }); 156 157 /** 158 * Tests that this subdialog can be opened via 159 * about:preferences?action=choose-what-to-sync#sync 160 */ 161 add_task(async function testDialogLaunchFromURI() { 162 await SpecialPowers.pushPrefEnv({ 163 set: [["identity.fxaccounts.enabled", true]], 164 }); 165 166 let dialogEventPromise = BrowserTestUtils.waitForEvent( 167 window, 168 "dialogopen", 169 true 170 ); 171 await BrowserTestUtils.withNewTab( 172 "about:preferences?action=choose-what-to-sync#sync", 173 async () => { 174 let dialogEvent = await dialogEventPromise; 175 Assert.equal( 176 dialogEvent.detail.dialog._frame.contentWindow.location, 177 "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml" 178 ); 179 } 180 ); 181 }); 182 183 // After CWTS is saved, we should immediately sync to update the server 184 add_task(async function testSyncCalledAfterSavingCWTS() { 185 await SpecialPowers.pushPrefEnv({ 186 set: [["identity.fxaccounts.enabled", true]], 187 }); 188 189 // Store original methods 190 const svc = Weave.Service; 191 const origLocked = svc._locked; 192 const origSync = svc.sync; 193 let syncCalls = 0; 194 195 // Override sync functions, emulate user not currently syncing 196 svc._locked = false; 197 svc.sync = () => { 198 syncCalls++; 199 return Promise.resolve(); 200 }; 201 202 // Open the dialog and accept to emulate user saving their options 203 await runWithCWTSDialog(async win => { 204 let doc = win.document; 205 let syncDialog = doc.getElementById("syncChooseOptions"); 206 207 let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload"); 208 syncDialog.acceptDialog(); 209 210 info("waiting for dialog to unload"); 211 await promiseUnloaded; 212 213 // Since _locked is false, sync() should fire right away. 214 await TestUtils.waitForCondition( 215 () => syncCalls == 1, 216 "Immediate sync() call when service._locked is false" 217 ); 218 }); 219 220 // Clean up 221 svc._locked = origLocked; 222 svc.sync = origSync; 223 }); 224 225 // After CWTS is saved and the user is still syncing, we should schedule a follow-up 226 // sync after the in-flight one 227 add_task(async function testSyncScheduledWhileSyncing() { 228 await SpecialPowers.pushPrefEnv({ 229 set: [["identity.fxaccounts.enabled", true]], 230 }); 231 232 // Store original methods 233 const svc = Weave.Service; 234 const origLocked = svc._locked; 235 const origSync = svc.sync; 236 let syncCalls = 0; 237 238 // Override sync functions, emulate user not currently syncing 239 svc._locked = true; 240 svc.sync = () => { 241 syncCalls++; 242 return Promise.resolve(); 243 }; 244 245 // Open the dialog and accept to emulate user saving their options 246 await runWithCWTSDialog(async win => { 247 let doc = win.document; 248 let syncDialog = doc.getElementById("syncChooseOptions"); 249 250 let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload"); 251 syncDialog.acceptDialog(); 252 253 info("waiting for dialog to unload"); 254 await promiseUnloaded; 255 256 // Should *not* have called svc.sync() immediately 257 Assert.equal(syncCalls, 0, "No immediate sync when _locked is true"); 258 259 // Now fire the “sync finished” notification 260 Services.obs.notifyObservers(null, "weave:service:sync:finish"); 261 262 // And wait for our queued sync() 263 await TestUtils.waitForCondition( 264 () => syncCalls === 1, 265 "Pending sync should fire once service finishes" 266 ); 267 }); 268 269 // Clean up 270 svc._locked = origLocked; 271 svc.sync = origSync; 272 }); 273 274 add_task(async function testTelemetrySentOnDialogAccept() { 275 Services.fog.testResetFOG(); 276 277 await SpecialPowers.pushPrefEnv({ 278 set: [ 279 ["services.sync.engine.addons", true], 280 ["services.sync.engine.bookmarks", false], 281 ["services.sync.engine.history", false], 282 ["services.sync.engine.tabs", false], 283 ["services.sync.engine.prefs", true], 284 ["services.sync.engine.passwords", true], 285 ["services.sync.engine.addresses", false], 286 ["services.sync.engine.creditcards", true], 287 288 ["identity.fxaccounts.enabled", true], 289 ], 290 }); 291 292 const expectedEngineSettings = { 293 "services.sync.engine.addons": false, 294 "services.sync.engine.bookmarks": true, 295 "services.sync.engine.history": true, 296 "services.sync.engine.tabs": true, 297 "services.sync.engine.prefs": false, 298 "services.sync.engine.passwords": false, 299 "services.sync.engine.addresses": true, 300 "services.sync.engine.creditcards": false, 301 }; 302 303 await openPreferencesViaOpenPreferencesAPI("paneGeneral", { 304 leaveOpen: true, 305 }); 306 307 // This will check if the callback was actually called during the test 308 let callbackCalled = false; 309 310 // Enabling all the sync UI is painful in tests, so we just open the dialog manually 311 let syncWindow = await openAndLoadSubDialog( 312 "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml", 313 null, 314 {}, 315 () => { 316 var expectedEnabledEngines = []; 317 var expectedDisabledEngines = []; 318 319 for (const [prefKey, prefValue] of Object.entries( 320 expectedEngineSettings 321 )) { 322 Assert.equal( 323 Services.prefs.getBoolPref(prefKey), 324 prefValue, 325 `${prefValue} is expected value` 326 ); 327 328 // Splitting engine settings by enablement to make it easier to test. 329 let engineName = prefKey.replace("services.sync.engine.", ""); 330 if (prefValue === true) { 331 expectedEnabledEngines.push(engineName); 332 } else { 333 expectedDisabledEngines.push(engineName); 334 } 335 } 336 callbackCalled = true; 337 338 const actual = Glean.syncSettings.save.testGetValue()[0]; 339 const expectedCategory = "sync_settings"; 340 const expectedName = "save"; 341 const actualEnabledEngines = actual.extra.enabled_engines.split(","); 342 const actualDisabledEngines = actual.extra.disabled_engines.split(","); 343 344 Assert.equal( 345 actual.category, 346 expectedCategory, 347 `telemetry category is ${expectedCategory}` 348 ); 349 Assert.equal( 350 actual.name, 351 expectedName, 352 `telemetry name is ${expectedName}` 353 ); 354 Assert.equal( 355 actualEnabledEngines.length, 356 expectedEnabledEngines.length, 357 `reported ${expectedEnabledEngines.length} engines enabled` 358 ); 359 Assert.equal( 360 actualDisabledEngines.length, 361 expectedDisabledEngines.length, 362 `reported ${expectedDisabledEngines.length} engines disabled` 363 ); 364 365 actualEnabledEngines.forEach(engine => { 366 Assert.ok( 367 expectedEnabledEngines.includes(engine), 368 `reported enabled engines should include ${engine} engine` 369 ); 370 }); 371 372 actualDisabledEngines.forEach(engine => { 373 Assert.ok( 374 expectedDisabledEngines.includes(engine), 375 `reported disabled engines should include ${engine} engine` 376 ); 377 }); 378 } 379 ); 380 381 Assert.ok(syncWindow, "Choose what to sync window opened"); 382 let syncChooseDialog = 383 syncWindow.document.getElementById("syncChooseOptions"); 384 let syncCheckboxes = syncChooseDialog.querySelectorAll( 385 "checkbox[preference]" 386 ); 387 388 [...syncCheckboxes].forEach(checkbox => { 389 // Setting the UI to match the predefined engine settings. 390 if ( 391 expectedEngineSettings[checkbox.getAttribute("preference")] !== 392 checkbox.checked 393 ) { 394 checkbox.click(); 395 } 396 }); 397 398 syncChooseDialog.acceptDialog(); 399 400 BrowserTestUtils.removeTab(gBrowser.selectedTab); 401 Assert.ok(callbackCalled, "Accept callback was called"); 402 403 await SpecialPowers.popPrefEnv(); 404 }); 405 406 async function runWithCWTSDialog(test) { 407 await openPreferencesViaOpenPreferencesAPI("paneSync", { leaveOpen: true }); 408 409 let promiseSubDialogLoaded = promiseLoadSubDialog( 410 "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml" 411 ); 412 gBrowser.contentWindow.SyncHelpers._chooseWhatToSync(true); 413 414 let win = await promiseSubDialogLoaded; 415 416 await test(win); 417 418 BrowserTestUtils.removeTab(gBrowser.selectedTab); 419 }