tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }