tor-browser

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

browser_sanitizeDialog.js (23034B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /**
      8 * Tests the sanitize dialog (a.k.a. the clear recent history dialog).
      9 * See bug 480169.
     10 *
     11 * The purpose of this test is not to fully flex the sanitize timespan code;
     12 * browser/base/content/test/sanitize/browser_sanitize-timespans.js does that.  This
     13 * test checks the UI of the dialog and makes sure it's correctly connected to
     14 * the sanitize timespan code.
     15 *
     16 * Some of this code, especially the history creation parts, was taken from
     17 * browser/base/content/test/sanitize/browser_sanitize-timespans.js.
     18 */
     19 
     20 ChromeUtils.defineESModuleGetters(this, {
     21  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
     22  Timer: "resource://gre/modules/Timer.sys.mjs",
     23 });
     24 
     25 /**
     26 * Ensures that the specified URIs are either cleared or not.
     27 *
     28 * @param aURIs
     29 *        Array of page URIs
     30 * @param aShouldBeCleared
     31 *        True if each visit to the URI should be cleared, false otherwise
     32 */
     33 async function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
     34  for (let uri of aURIs) {
     35    let visited = await PlacesUtils.history.hasVisits(uri);
     36    Assert.equal(
     37      visited,
     38      !aShouldBeCleared,
     39      `history visit ${uri.spec} should ${
     40        aShouldBeCleared ? "no longer" : "still"
     41      } exist`
     42    );
     43  }
     44 }
     45 
     46 add_setup(async function () {
     47  requestLongerTimeout(3);
     48  await blankSlate();
     49  registerCleanupFunction(async function () {
     50    await blankSlate();
     51    await PlacesTestUtils.promiseAsyncUpdates();
     52  });
     53 
     54  await SpecialPowers.pushPrefEnv({
     55    set: [["privacy.sanitize.useOldClearHistoryDialog", true]],
     56  });
     57 });
     58 
     59 /**
     60 * Initializes the dialog to its default state.
     61 */
     62 add_task(async function default_state() {
     63  let dh = new DialogHelper();
     64  dh.onload = function () {
     65    // Select "Last Hour"
     66    this.selectDuration(Sanitizer.TIMESPAN_HOUR);
     67    this.acceptDialog();
     68  };
     69  dh.open();
     70  await dh.promiseClosed;
     71 });
     72 
     73 /**
     74 * Cancels the dialog, makes sure history not cleared.
     75 */
     76 add_task(async function test_cancel() {
     77  // Add history (within the past hour)
     78  let uris = [];
     79  let places = [];
     80  let pURI;
     81  for (let i = 0; i < 30; i++) {
     82    pURI = makeURI("https://" + i + "-minutes-ago.com/");
     83    places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(i) });
     84    uris.push(pURI);
     85  }
     86  await PlacesTestUtils.addVisits(places);
     87 
     88  let dh = new DialogHelper();
     89  dh.onload = function () {
     90    this.selectDuration(Sanitizer.TIMESPAN_HOUR);
     91    this.checkPrefCheckbox("history", false);
     92    this.cancelDialog();
     93  };
     94  dh.onunload = async function () {
     95    await promiseHistoryClearedState(uris, false);
     96    await blankSlate();
     97    await promiseHistoryClearedState(uris, true);
     98  };
     99  dh.open();
    100  await dh.promiseClosed;
    101 });
    102 
    103 /**
    104 * Ensures that the combined history-downloads checkbox clears both history
    105 * visits and downloads when checked; the dialog respects simple timespan.
    106 */
    107 add_task(async function test_history_downloads_checked() {
    108  // Add downloads (within the past hour).
    109  let downloadIDs = [];
    110  for (let i = 0; i < 5; i++) {
    111    await addDownloadWithMinutesAgo(downloadIDs, i);
    112  }
    113  // Add downloads (over an hour ago).
    114  let olderDownloadIDs = [];
    115  for (let i = 0; i < 5; i++) {
    116    await addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i);
    117  }
    118 
    119  // Add history (within the past hour).
    120  let uris = [];
    121  let places = [];
    122  let pURI;
    123  for (let i = 0; i < 30; i++) {
    124    pURI = makeURI("https://" + i + "-minutes-ago.com/");
    125    places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(i) });
    126    uris.push(pURI);
    127  }
    128  // Add history (over an hour ago).
    129  let olderURIs = [];
    130  for (let i = 0; i < 5; i++) {
    131    pURI = makeURI("https://" + (61 + i) + "-minutes-ago.com/");
    132    places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i) });
    133    olderURIs.push(pURI);
    134  }
    135  let promiseSanitized = promiseSanitizationComplete();
    136 
    137  await PlacesTestUtils.addVisits(places);
    138 
    139  let dh = new DialogHelper();
    140  dh.onload = function () {
    141    this.selectDuration(Sanitizer.TIMESPAN_HOUR);
    142    this.checkPrefCheckbox("history", true);
    143    this.acceptDialog();
    144  };
    145  dh.onunload = async function () {
    146    intPrefIs(
    147      "sanitize.timeSpan",
    148      Sanitizer.TIMESPAN_HOUR,
    149      "timeSpan pref should be hour after accepting dialog with " +
    150        "hour selected"
    151    );
    152    boolPrefIs(
    153      "cpd.history",
    154      true,
    155      "history pref should be true after accepting dialog with " +
    156        "history checkbox checked"
    157    );
    158    boolPrefIs(
    159      "cpd.downloads",
    160      true,
    161      "downloads pref should be true after accepting dialog with " +
    162        "history checkbox checked"
    163    );
    164 
    165    await promiseSanitized;
    166 
    167    // History visits and downloads within one hour should be cleared.
    168    await promiseHistoryClearedState(uris, true);
    169    await ensureDownloadsClearedState(downloadIDs, true);
    170 
    171    // Visits and downloads > 1 hour should still exist.
    172    await promiseHistoryClearedState(olderURIs, false);
    173    await ensureDownloadsClearedState(olderDownloadIDs, false);
    174 
    175    // OK, done, cleanup after ourselves.
    176    await blankSlate();
    177    await promiseHistoryClearedState(olderURIs, true);
    178    await ensureDownloadsClearedState(olderDownloadIDs, true);
    179  };
    180  dh.open();
    181  await dh.promiseClosed;
    182 });
    183 
    184 /**
    185 * Ensures that the combined history-downloads checkbox removes neither
    186 * history visits nor downloads when not checked.
    187 */
    188 add_task(async function test_history_downloads_unchecked() {
    189  // Add form entries
    190  let formEntries = [];
    191 
    192  for (let i = 0; i < 5; i++) {
    193    formEntries.push(await promiseAddFormEntryWithMinutesAgo(i));
    194  }
    195 
    196  // Add downloads (within the past hour).
    197  let downloadIDs = [];
    198  for (let i = 0; i < 5; i++) {
    199    await addDownloadWithMinutesAgo(downloadIDs, i);
    200  }
    201 
    202  // Add history, downloads, form entries (within the past hour).
    203  let uris = [];
    204  let places = [];
    205  let pURI;
    206  for (let i = 0; i < 5; i++) {
    207    pURI = makeURI("https://" + i + "-minutes-ago.com/");
    208    places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(i) });
    209    uris.push(pURI);
    210  }
    211 
    212  await PlacesTestUtils.addVisits(places);
    213  let dh = new DialogHelper();
    214  dh.onload = function () {
    215    is(
    216      this.isWarningPanelVisible(),
    217      false,
    218      "Warning panel should be hidden after previously accepting dialog " +
    219        "with a predefined timespan"
    220    );
    221    this.selectDuration(Sanitizer.TIMESPAN_HOUR);
    222 
    223    // Remove only form entries, leave history (including downloads).
    224    this.checkPrefCheckbox("history", false);
    225    this.checkPrefCheckbox("formdata", true);
    226    this.acceptDialog();
    227  };
    228  dh.onunload = async function () {
    229    intPrefIs(
    230      "sanitize.timeSpan",
    231      Sanitizer.TIMESPAN_HOUR,
    232      "timeSpan pref should be hour after accepting dialog with " +
    233        "hour selected"
    234    );
    235    boolPrefIs(
    236      "cpd.history",
    237      false,
    238      "history pref should be false after accepting dialog with " +
    239        "history checkbox unchecked"
    240    );
    241    boolPrefIs(
    242      "cpd.downloads",
    243      false,
    244      "downloads pref should be false after accepting dialog with " +
    245        "history checkbox unchecked"
    246    );
    247 
    248    // Of the three only form entries should be cleared.
    249    await promiseHistoryClearedState(uris, false);
    250    await ensureDownloadsClearedState(downloadIDs, false);
    251 
    252    for (let entry of formEntries) {
    253      let exists = await formNameExists(entry);
    254      ok(!exists, "form entry " + entry + " should no longer exist");
    255    }
    256 
    257    // OK, done, cleanup after ourselves.
    258    await blankSlate();
    259    await promiseHistoryClearedState(uris, true);
    260    await ensureDownloadsClearedState(downloadIDs, true);
    261  };
    262  dh.open();
    263  await dh.promiseClosed;
    264 });
    265 
    266 /**
    267 * Ensures that the "Everything" duration option works.
    268 */
    269 add_task(async function test_everything() {
    270  // Add history.
    271  let uris = [];
    272  let places = [];
    273  let pURI;
    274  // within past hour, within past two hours, within past four hours and
    275  // outside past four hours
    276  [10, 70, 130, 250].forEach(function (aValue) {
    277    pURI = makeURI("https://" + aValue + "-minutes-ago.com/");
    278    places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(aValue) });
    279    uris.push(pURI);
    280  });
    281 
    282  let promiseSanitized = promiseSanitizationComplete();
    283 
    284  await PlacesTestUtils.addVisits(places);
    285  let dh = new DialogHelper();
    286  dh.onload = function () {
    287    is(
    288      this.isWarningPanelVisible(),
    289      false,
    290      "Warning panel should be hidden after previously accepting dialog " +
    291        "with a predefined timespan"
    292    );
    293    this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
    294    this.checkPrefCheckbox("history", true);
    295    this.acceptDialog();
    296  };
    297  dh.onunload = async function () {
    298    await promiseSanitized;
    299    intPrefIs(
    300      "sanitize.timeSpan",
    301      Sanitizer.TIMESPAN_EVERYTHING,
    302      "timeSpan pref should be everything after accepting dialog " +
    303        "with everything selected"
    304    );
    305 
    306    await promiseHistoryClearedState(uris, true);
    307  };
    308  dh.open();
    309  await dh.promiseClosed;
    310 });
    311 
    312 /**
    313 * Ensures that the "Everything" warning is visible on dialog open after
    314 * the previous test.
    315 */
    316 add_task(async function test_everything_warning() {
    317  // Add history.
    318  let uris = [];
    319  let places = [];
    320  let pURI;
    321  // within past hour, within past two hours, within past four hours and
    322  // outside past four hours
    323  [10, 70, 130, 250].forEach(function (aValue) {
    324    pURI = makeURI("https://" + aValue + "-minutes-ago.com/");
    325    places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(aValue) });
    326    uris.push(pURI);
    327  });
    328 
    329  let promiseSanitized = promiseSanitizationComplete();
    330 
    331  await PlacesTestUtils.addVisits(places);
    332  let dh = new DialogHelper();
    333  dh.onload = function () {
    334    is(
    335      this.isWarningPanelVisible(),
    336      true,
    337      "Warning panel should be visible after previously accepting dialog " +
    338        "with clearing everything"
    339    );
    340    this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
    341    this.checkPrefCheckbox("history", true);
    342    this.acceptDialog();
    343  };
    344  dh.onunload = async function () {
    345    intPrefIs(
    346      "sanitize.timeSpan",
    347      Sanitizer.TIMESPAN_EVERYTHING,
    348      "timeSpan pref should be everything after accepting dialog " +
    349        "with everything selected"
    350    );
    351 
    352    await promiseSanitized;
    353 
    354    await promiseHistoryClearedState(uris, true);
    355  };
    356  dh.open();
    357  await dh.promiseClosed;
    358 });
    359 
    360 /**
    361 * The next three tests checks that when a certain history item cannot be
    362 * cleared then the checkbox should be both disabled and unchecked.
    363 * In addition, we ensure that this behavior does not modify the preferences.
    364 */
    365 add_task(async function test_cannot_clear_history() {
    366  // Add form entries
    367  let formEntries = [await promiseAddFormEntryWithMinutesAgo(10)];
    368 
    369  let promiseSanitized = promiseSanitizationComplete();
    370 
    371  // Add history.
    372  let pURI = makeURI("https://" + 10 + "-minutes-ago.com/");
    373  await PlacesTestUtils.addVisits({
    374    uri: pURI,
    375    visitDate: visitTimeForMinutesAgo(10),
    376  });
    377  let uris = [pURI];
    378 
    379  let dh = new DialogHelper();
    380  dh.onload = function () {
    381    // Check that the relevant checkboxes are enabled
    382    var cb = this.win.document.querySelectorAll(
    383      "checkbox[preference='privacy.cpd.formdata']"
    384    );
    385    ok(
    386      cb.length == 1 && !cb[0].disabled,
    387      "There is formdata, checkbox to clear formdata should be enabled."
    388    );
    389 
    390    cb = this.win.document.querySelectorAll(
    391      "checkbox[preference='privacy.cpd.history']"
    392    );
    393    ok(
    394      cb.length == 1 && !cb[0].disabled,
    395      "There is history, checkbox to clear history should be enabled."
    396    );
    397 
    398    this.checkAllCheckboxes();
    399    this.acceptDialog();
    400  };
    401  dh.onunload = async function () {
    402    await promiseSanitized;
    403 
    404    await promiseHistoryClearedState(uris, true);
    405 
    406    let exists = await formNameExists(formEntries[0]);
    407    ok(!exists, "form entry " + formEntries[0] + " should no longer exist");
    408  };
    409  dh.open();
    410  await dh.promiseClosed;
    411 });
    412 
    413 add_task(async function test_no_formdata_history_to_clear() {
    414  let promiseSanitized = promiseSanitizationComplete();
    415  let dh = new DialogHelper();
    416  dh.onload = function () {
    417    boolPrefIs(
    418      "cpd.history",
    419      true,
    420      "history pref should be true after accepting dialog with " +
    421        "history checkbox checked"
    422    );
    423    boolPrefIs(
    424      "cpd.formdata",
    425      true,
    426      "formdata pref should be true after accepting dialog with " +
    427        "formdata checkbox checked"
    428    );
    429 
    430    var cb = this.win.document.querySelectorAll(
    431      "checkbox[preference='privacy.cpd.history']"
    432    );
    433    ok(
    434      cb.length == 1 && !cb[0].disabled && cb[0].checked,
    435      "There is no history, but history checkbox should always be enabled " +
    436        "and will be checked from previous preference."
    437    );
    438 
    439    this.acceptDialog();
    440  };
    441  dh.open();
    442  await dh.promiseClosed;
    443  await promiseSanitized;
    444 });
    445 
    446 add_task(async function test_form_entries() {
    447  let formEntry = await promiseAddFormEntryWithMinutesAgo(10);
    448 
    449  let promiseSanitized = promiseSanitizationComplete();
    450 
    451  let dh = new DialogHelper();
    452  dh.onload = function () {
    453    boolPrefIs(
    454      "cpd.formdata",
    455      true,
    456      "formdata pref should persist previous value after accepting " +
    457        "dialog where you could not clear formdata."
    458    );
    459 
    460    var cb = this.win.document.querySelectorAll(
    461      "checkbox[preference='privacy.cpd.formdata']"
    462    );
    463 
    464    info(
    465      "There exists formEntries so the checkbox should be in sync with the pref."
    466    );
    467    is(cb.length, 1, "There is only one checkbox for form data");
    468    ok(!cb[0].disabled, "The checkbox is enabled");
    469    ok(cb[0].checked, "The checkbox is checked");
    470 
    471    this.acceptDialog();
    472  };
    473  dh.onunload = async function () {
    474    await promiseSanitized;
    475    let exists = await formNameExists(formEntry);
    476    ok(!exists, "form entry " + formEntry + " should no longer exist");
    477  };
    478  dh.open();
    479  await dh.promiseClosed;
    480 });
    481 
    482 // Test for offline apps permission deletion
    483 add_task(async function test_offline_apps_permissions() {
    484  // Prepare stuff, we will work with www.example.com
    485  var URL = "https://www.example.com";
    486  var URI = makeURI(URL);
    487  var principal = Services.scriptSecurityManager.createContentPrincipal(
    488    URI,
    489    {}
    490  );
    491 
    492  let promiseSanitized = promiseSanitizationComplete();
    493 
    494  // Open the dialog
    495  let dh = new DialogHelper();
    496  dh.onload = function () {
    497    this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
    498    // Clear only offlineApps
    499    this.uncheckAllCheckboxes();
    500    this.checkPrefCheckbox("siteSettings", true);
    501    this.acceptDialog();
    502  };
    503  dh.onunload = async function () {
    504    await promiseSanitized;
    505 
    506    // Check all has been deleted (privileges, data, cache)
    507    is(
    508      Services.perms.testPermissionFromPrincipal(principal, "offline-app"),
    509      0,
    510      "offline-app permissions removed"
    511    );
    512  };
    513  dh.open();
    514  await dh.promiseClosed;
    515 });
    516 
    517 var now_mSec = Date.now();
    518 var now_uSec = now_mSec * 1000;
    519 
    520 /**
    521 * This wraps the dialog and provides some convenience methods for interacting
    522 * with it.
    523 *
    524 * @param browserWin (optional)
    525 *        The browser window that the dialog is expected to open in. If not
    526 *        supplied, the initial browser window of the test run is used.
    527 */
    528 function DialogHelper(browserWin = window) {
    529  this._browserWin = browserWin;
    530  this.win = null;
    531  this.promiseClosed = new Promise(resolve => {
    532    this._resolveClosed = resolve;
    533  });
    534 }
    535 
    536 DialogHelper.prototype = {
    537  /**
    538   * "Presses" the dialog's OK button.
    539   */
    540  acceptDialog() {
    541    let dialogEl = this.win.document.querySelector("dialog");
    542    is(
    543      dialogEl.getButton("accept").disabled,
    544      false,
    545      "Dialog's OK button should not be disabled"
    546    );
    547    dialogEl.acceptDialog();
    548  },
    549 
    550  /**
    551   * "Presses" the dialog's Cancel button.
    552   */
    553  cancelDialog() {
    554    this.win.document.querySelector("dialog").cancelDialog();
    555  },
    556 
    557  /**
    558   * (Un)checks a history scope checkbox (browser & download history,
    559   * form history, etc.).
    560   *
    561   * @param aPrefName
    562   *        The final portion of the checkbox's privacy.cpd.* preference name
    563   * @param aCheckState
    564   *        True if the checkbox should be checked, false otherwise
    565   */
    566  checkPrefCheckbox(aPrefName, aCheckState) {
    567    var pref = "privacy.cpd." + aPrefName;
    568    var cb = this.win.document.querySelectorAll(
    569      "checkbox[preference='" + pref + "']"
    570    );
    571    is(cb.length, 1, "found checkbox for " + pref + " preference");
    572    if (cb[0].checked != aCheckState) {
    573      cb[0].click();
    574    }
    575  },
    576 
    577  /**
    578   * Makes sure all the checkboxes are checked.
    579   */
    580  _checkAllCheckboxesCustom(check) {
    581    var cb = this.win.document.querySelectorAll("checkbox[preference]");
    582    Assert.greater(cb.length, 1, "found checkboxes for preferences");
    583    for (var i = 0; i < cb.length; ++i) {
    584      var pref = this.win.Preferences.get(cb[i].getAttribute("preference"));
    585      if (!!pref.value ^ check) {
    586        cb[i].click();
    587      }
    588    }
    589  },
    590 
    591  checkAllCheckboxes() {
    592    this._checkAllCheckboxesCustom(true);
    593  },
    594 
    595  uncheckAllCheckboxes() {
    596    this._checkAllCheckboxesCustom(false);
    597  },
    598 
    599  /**
    600   * @return The dialog's duration dropdown
    601   */
    602  getDurationDropdown() {
    603    return this.win.document.getElementById("sanitizeDurationChoice");
    604  },
    605 
    606  /**
    607   * @return The clear-everything warning box
    608   */
    609  getWarningPanel() {
    610    return this.win.document.getElementById("sanitizeEverythingWarningBox");
    611  },
    612 
    613  /**
    614   * @return True if the "Everything" warning panel is visible (as opposed to
    615   *         the tree)
    616   */
    617  isWarningPanelVisible() {
    618    return !this.getWarningPanel().hidden;
    619  },
    620 
    621  /**
    622   * Opens the clear recent history dialog.  Before calling this, set
    623   * this.onload to a function to execute onload.  It should close the dialog
    624   * when done so that the tests may continue.  Set this.onunload to a function
    625   * to execute onunload.  this.onunload is optional. If it returns true, the
    626   * caller is expected to call promiseAsyncUpdates at some point; if false is
    627   * returned, promiseAsyncUpdates is called automatically.
    628   */
    629  async open() {
    630    let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
    631      null,
    632      "chrome://browser/content/sanitize.xhtml",
    633      {
    634        isSubDialog: true,
    635      }
    636    );
    637 
    638    executeSoon(() => {
    639      Sanitizer.showUI(this._browserWin);
    640    });
    641 
    642    this.win = await dialogPromise;
    643    this.win.addEventListener(
    644      "load",
    645      () => {
    646        // Run onload on next tick so that gSanitizePromptDialog.init can run first.
    647        executeSoon(() => this.onload());
    648      },
    649      { once: true }
    650    );
    651 
    652    this.win.addEventListener(
    653      "unload",
    654      () => {
    655        // Some exceptions that reach here don't reach the test harness, but
    656        // ok()/is() do...
    657        (async () => {
    658          if (this.onunload) {
    659            await this.onunload();
    660          }
    661          await PlacesTestUtils.promiseAsyncUpdates();
    662          this._resolveClosed();
    663          this.win = null;
    664        })();
    665      },
    666      { once: true }
    667    );
    668  },
    669 
    670  /**
    671   * Selects a duration in the duration dropdown.
    672   *
    673   * @param aDurVal
    674   *        One of the Sanitizer.TIMESPAN_* values
    675   */
    676  selectDuration(aDurVal) {
    677    this.getDurationDropdown().value = aDurVal;
    678    if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
    679      is(
    680        this.isWarningPanelVisible(),
    681        true,
    682        "Warning panel should be visible for TIMESPAN_EVERYTHING"
    683      );
    684    } else {
    685      is(
    686        this.isWarningPanelVisible(),
    687        false,
    688        "Warning panel should not be visible for non-TIMESPAN_EVERYTHING"
    689      );
    690    }
    691  },
    692 };
    693 
    694 /**
    695 * Adds a download to history.
    696 *
    697 * @param aMinutesAgo
    698 *        The download will be downloaded this many minutes ago
    699 */
    700 async function addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) {
    701  let publicList = await Downloads.getList(Downloads.PUBLIC);
    702 
    703  let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
    704  let download = await Downloads.createDownload({
    705    source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
    706    target: name,
    707  });
    708  download.startTime = new Date(now_mSec - aMinutesAgo * kMsecPerMin);
    709  download.canceled = true;
    710  publicList.add(download);
    711 
    712  ok(
    713    await downloadExists(name),
    714    "Sanity check: download " + name + " should exist after creating it"
    715  );
    716 
    717  aExpectedPathList.push(name);
    718 }
    719 
    720 /**
    721 * Adds a form entry to history.
    722 *
    723 * @param aMinutesAgo
    724 *        The entry will be added this many minutes ago
    725 */
    726 function promiseAddFormEntryWithMinutesAgo(aMinutesAgo) {
    727  let name = aMinutesAgo + "-minutes-ago";
    728 
    729  // Artifically age the entry to the proper vintage.
    730  let timestamp = now_uSec - aMinutesAgo * kUsecPerMin;
    731 
    732  return FormHistory.update({
    733    op: "add",
    734    fieldname: name,
    735    value: "dummy",
    736    firstUsed: timestamp,
    737  });
    738 }
    739 
    740 /**
    741 * Checks if a form entry exists.
    742 */
    743 async function formNameExists(name) {
    744  return !!(await FormHistory.count({ fieldname: name }));
    745 }
    746 
    747 /**
    748 * Ensures that the given pref is the expected value.
    749 *
    750 * @param aPrefName
    751 *        The pref's sub-branch under the privacy branch
    752 * @param aExpectedVal
    753 *        The pref's expected value
    754 * @param aMsg
    755 *        Passed to is()
    756 */
    757 function boolPrefIs(aPrefName, aExpectedVal, aMsg) {
    758  is(Services.prefs.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg);
    759 }
    760 
    761 /**
    762 * Checks to see if the download with the specified path exists.
    763 *
    764 * @param  aPath
    765 *         The path of the download to check
    766 * @return True if the download exists, false otherwise
    767 */
    768 async function downloadExists(aPath) {
    769  let publicList = await Downloads.getList(Downloads.PUBLIC);
    770  let listArray = await publicList.getAll();
    771  return listArray.some(i => i.target.path == aPath);
    772 }
    773 
    774 /**
    775 * Ensures that the specified downloads are either cleared or not.
    776 *
    777 * @param aDownloadIDs
    778 *        Array of download database IDs
    779 * @param aShouldBeCleared
    780 *        True if each download should be cleared, false otherwise
    781 */
    782 async function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
    783  let niceStr = aShouldBeCleared ? "no longer" : "still";
    784  for (let id of aDownloadIDs) {
    785    is(
    786      await downloadExists(id),
    787      !aShouldBeCleared,
    788      "download " + id + " should " + niceStr + " exist"
    789    );
    790  }
    791 }
    792 
    793 /**
    794 * Ensures that the given pref is the expected value.
    795 *
    796 * @param aPrefName
    797 *        The pref's sub-branch under the privacy branch
    798 * @param aExpectedVal
    799 *        The pref's expected value
    800 * @param aMsg
    801 *        Passed to is()
    802 */
    803 function intPrefIs(aPrefName, aExpectedVal, aMsg) {
    804  is(Services.prefs.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg);
    805 }
    806 
    807 /**
    808 * Creates a visit time.
    809 *
    810 * @param aMinutesAgo
    811 *        The visit will be visited this many minutes ago
    812 */
    813 function visitTimeForMinutesAgo(aMinutesAgo) {
    814  return now_uSec - aMinutesAgo * kUsecPerMin;
    815 }