/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { Service } = ChromeUtils.importESModule( "resource://services-sync/service.sys.mjs" ); const { UIState } = ChromeUtils.importESModule( "resource://services-sync/UIState.sys.mjs" ); // This obj will be used in both tests // First test makes sure accepting the preferences matches these values // Second test makes sure the cancel dialog STILL matches these values const syncPrefs = { "services.sync.engine.addons": false, "services.sync.engine.bookmarks": true, "services.sync.engine.history": true, "services.sync.engine.tabs": false, "services.sync.engine.prefs": false, "services.sync.engine.passwords": false, "services.sync.engine.addresses": false, "services.sync.engine.creditcards": false, }; add_setup(async () => { UIState._internal.notifyStateUpdated = () => {}; const origNotifyStateUpdated = UIState._internal.notifyStateUpdated; const origGet = UIState.get; UIState.get = () => { return { status: UIState.STATUS_SIGNED_IN, email: "foo@bar.com" }; }; registerCleanupFunction(() => { UIState._internal.notifyStateUpdated = origNotifyStateUpdated; UIState.get = origGet; }); }); /** * We don't actually enable sync here, but we just check that the preferences are correct * when the callback gets hit (accepting/cancelling the dialog) * See https://bugzilla.mozilla.org/show_bug.cgi?id=1584132. */ add_task(async function testDialogAccept() { await SpecialPowers.pushPrefEnv({ set: [["identity.fxaccounts.enabled", true]], }); await openPreferencesViaOpenPreferencesAPI("paneGeneral", { leaveOpen: true, }); // This will check if the callback was actually called during the test let callbackCalled = false; // Enabling all the sync UI is painful in tests, so we just open the dialog manually let syncWindow = await openAndLoadSubDialog( "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml", null, {}, () => { for (const [prefKey, prefValue] of Object.entries(syncPrefs)) { Assert.equal( Services.prefs.getBoolPref(prefKey), prefValue, `${prefValue} is expected value` ); } callbackCalled = true; } ); Assert.ok(syncWindow, "Choose what to sync window opened"); let syncChooseDialog = syncWindow.document.getElementById("syncChooseOptions"); let syncCheckboxes = syncChooseDialog.querySelectorAll( "checkbox[preference]" ); // Adjust the checkbox values to the expectedValues in the list [...syncCheckboxes].forEach(checkbox => { if (syncPrefs[checkbox.getAttribute("preference")] !== checkbox.checked) { checkbox.click(); } }); syncChooseDialog.acceptDialog(); BrowserTestUtils.removeTab(gBrowser.selectedTab); Assert.ok(callbackCalled, "Accept callback was called"); }); add_task(async function testDialogCancel() { const cancelSyncPrefs = { "services.sync.engine.addons": true, "services.sync.engine.bookmarks": false, "services.sync.engine.history": true, "services.sync.engine.tabs": true, "services.sync.engine.prefs": false, "services.sync.engine.passwords": true, "services.sync.engine.addresses": true, "services.sync.engine.creditcards": false, }; await SpecialPowers.pushPrefEnv({ set: [["identity.fxaccounts.enabled", true]], }); await openPreferencesViaOpenPreferencesAPI("paneGeneral", { leaveOpen: true, }); // This will check if the callback was actually called during the test let callbackCalled = false; // Enabling all the sync UI is painful in tests, so we just open the dialog manually let syncWindow = await openAndLoadSubDialog( "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml", null, {}, () => { // We want to test against our previously accepted values in the last test for (const [prefKey, prefValue] of Object.entries(syncPrefs)) { Assert.equal( Services.prefs.getBoolPref(prefKey), prefValue, `${prefValue} is expected value` ); } callbackCalled = true; } ); ok(syncWindow, "Choose what to sync window opened"); let syncChooseDialog = syncWindow.document.getElementById("syncChooseOptions"); let syncCheckboxes = syncChooseDialog.querySelectorAll( "checkbox[preference]" ); // This time we're adjusting to the cancel list [...syncCheckboxes].forEach(checkbox => { if ( cancelSyncPrefs[checkbox.getAttribute("preference")] !== checkbox.checked ) { checkbox.click(); } }); syncChooseDialog.cancelDialog(); BrowserTestUtils.removeTab(gBrowser.selectedTab); Assert.ok(callbackCalled, "Cancel callback was called"); }); /** * Tests that this subdialog can be opened via * about:preferences?action=choose-what-to-sync#sync */ add_task(async function testDialogLaunchFromURI() { await SpecialPowers.pushPrefEnv({ set: [["identity.fxaccounts.enabled", true]], }); let dialogEventPromise = BrowserTestUtils.waitForEvent( window, "dialogopen", true ); await BrowserTestUtils.withNewTab( "about:preferences?action=choose-what-to-sync#sync", async () => { let dialogEvent = await dialogEventPromise; Assert.equal( dialogEvent.detail.dialog._frame.contentWindow.location, "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml" ); } ); }); // After CWTS is saved, we should immediately sync to update the server add_task(async function testSyncCalledAfterSavingCWTS() { await SpecialPowers.pushPrefEnv({ set: [["identity.fxaccounts.enabled", true]], }); // Store original methods const svc = Weave.Service; const origLocked = svc._locked; const origSync = svc.sync; let syncCalls = 0; // Override sync functions, emulate user not currently syncing svc._locked = false; svc.sync = () => { syncCalls++; return Promise.resolve(); }; // Open the dialog and accept to emulate user saving their options await runWithCWTSDialog(async win => { let doc = win.document; let syncDialog = doc.getElementById("syncChooseOptions"); let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload"); syncDialog.acceptDialog(); info("waiting for dialog to unload"); await promiseUnloaded; // Since _locked is false, sync() should fire right away. await TestUtils.waitForCondition( () => syncCalls == 1, "Immediate sync() call when service._locked is false" ); }); // Clean up svc._locked = origLocked; svc.sync = origSync; }); // After CWTS is saved and the user is still syncing, we should schedule a follow-up // sync after the in-flight one add_task(async function testSyncScheduledWhileSyncing() { await SpecialPowers.pushPrefEnv({ set: [["identity.fxaccounts.enabled", true]], }); // Store original methods const svc = Weave.Service; const origLocked = svc._locked; const origSync = svc.sync; let syncCalls = 0; // Override sync functions, emulate user not currently syncing svc._locked = true; svc.sync = () => { syncCalls++; return Promise.resolve(); }; // Open the dialog and accept to emulate user saving their options await runWithCWTSDialog(async win => { let doc = win.document; let syncDialog = doc.getElementById("syncChooseOptions"); let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload"); syncDialog.acceptDialog(); info("waiting for dialog to unload"); await promiseUnloaded; // Should *not* have called svc.sync() immediately Assert.equal(syncCalls, 0, "No immediate sync when _locked is true"); // Now fire the “sync finished” notification Services.obs.notifyObservers(null, "weave:service:sync:finish"); // And wait for our queued sync() await TestUtils.waitForCondition( () => syncCalls === 1, "Pending sync should fire once service finishes" ); }); // Clean up svc._locked = origLocked; svc.sync = origSync; }); add_task(async function testTelemetrySentOnDialogAccept() { Services.fog.testResetFOG(); await SpecialPowers.pushPrefEnv({ set: [ ["services.sync.engine.addons", true], ["services.sync.engine.bookmarks", false], ["services.sync.engine.history", false], ["services.sync.engine.tabs", false], ["services.sync.engine.prefs", true], ["services.sync.engine.passwords", true], ["services.sync.engine.addresses", false], ["services.sync.engine.creditcards", true], ["identity.fxaccounts.enabled", true], ], }); const expectedEngineSettings = { "services.sync.engine.addons": false, "services.sync.engine.bookmarks": true, "services.sync.engine.history": true, "services.sync.engine.tabs": true, "services.sync.engine.prefs": false, "services.sync.engine.passwords": false, "services.sync.engine.addresses": true, "services.sync.engine.creditcards": false, }; await openPreferencesViaOpenPreferencesAPI("paneGeneral", { leaveOpen: true, }); // This will check if the callback was actually called during the test let callbackCalled = false; // Enabling all the sync UI is painful in tests, so we just open the dialog manually let syncWindow = await openAndLoadSubDialog( "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml", null, {}, async () => { var expectedEnabledEngines = []; var expectedDisabledEngines = []; for (const [prefKey, prefValue] of Object.entries( expectedEngineSettings )) { Assert.equal( Services.prefs.getBoolPref(prefKey), prefValue, `${prefValue} is expected value` ); // Splitting engine settings by enablement to make it easier to test. let engineName = prefKey.replace("services.sync.engine.", ""); if (prefValue === true) { expectedEnabledEngines.push(engineName); } else { expectedDisabledEngines.push(engineName); } } callbackCalled = true; const actual = await Glean.syncSettings.save.testGetValue()[0]; const expectedCategory = "sync_settings"; const expectedName = "save"; const actualEnabledEngines = actual.extra.enabled_engines.split(","); const actualDisabledEngines = actual.extra.disabled_engines.split(","); Assert.equal( actual.category, expectedCategory, `telemetry category is ${expectedCategory}` ); Assert.equal( actual.name, expectedName, `telemetry name is ${expectedName}` ); Assert.equal( actualEnabledEngines.length, expectedEnabledEngines.length, `reported ${expectedEnabledEngines.length} engines enabled` ); Assert.equal( actualDisabledEngines.length, expectedDisabledEngines.length, `reported ${expectedDisabledEngines.length} engines disabled` ); actualEnabledEngines.forEach(engine => { Assert.ok( expectedEnabledEngines.includes(engine), `reported enabled engines should include ${engine} engine` ); }); actualDisabledEngines.forEach(engine => { Assert.ok( expectedDisabledEngines.includes(engine), `reported disabled engines should include ${engine} engine` ); }); } ); Assert.ok(syncWindow, "Choose what to sync window opened"); let syncChooseDialog = syncWindow.document.getElementById("syncChooseOptions"); let syncCheckboxes = syncChooseDialog.querySelectorAll( "checkbox[preference]" ); [...syncCheckboxes].forEach(checkbox => { // Setting the UI to match the predefined engine settings. if ( expectedEngineSettings[checkbox.getAttribute("preference")] !== checkbox.checked ) { checkbox.click(); } }); syncChooseDialog.acceptDialog(); BrowserTestUtils.removeTab(gBrowser.selectedTab); Assert.ok(callbackCalled, "Accept callback was called"); await SpecialPowers.popPrefEnv(); }); async function runWithCWTSDialog(test) { await openPreferencesViaOpenPreferencesAPI("paneSync", { leaveOpen: true }); let promiseSubDialogLoaded = promiseLoadSubDialog( "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml" ); gBrowser.contentWindow.gSyncPane._chooseWhatToSync(true); let win = await promiseSubDialogLoaded; await test(win); BrowserTestUtils.removeTab(gBrowser.selectedTab); }