tor-browser

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

head.js (22773B)


      1 ChromeUtils.defineESModuleGetters(this, {
      2  Downloads: "resource://gre/modules/Downloads.sys.mjs",
      3  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
      4  PermissionTestUtils: "resource://testing-common/PermissionTestUtils.sys.mjs",
      5  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
      6  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
      7  FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs",
      8  Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
      9  SiteDataTestUtils: "resource://testing-common/SiteDataTestUtils.sys.mjs",
     10 });
     11 
     12 const kMsecPerMin = 60 * 1000;
     13 const kUsecPerMin = kMsecPerMin * 1000;
     14 let today = Date.now() - new Date().setHours(0, 0, 0, 0);
     15 let nowMSec = Date.now();
     16 let nowUSec = nowMSec * 1000;
     17 const TEST_TARGET_FILE_NAME = "test-download.txt";
     18 const TEST_QUOTA_USAGE_HOST = "example.com";
     19 const TEST_QUOTA_USAGE_ORIGIN = "https://" + TEST_QUOTA_USAGE_HOST;
     20 const TEST_QUOTA_USAGE_URL =
     21  getRootDirectory(gTestPath).replace(
     22    "chrome://mochitests/content",
     23    TEST_QUOTA_USAGE_ORIGIN
     24  ) + "site_data_test.html";
     25 const SITE_ORIGINS = [
     26  "https://www.example.com",
     27  "https://example.org",
     28  "http://localhost:8000",
     29  "http://localhost:3000",
     30 ];
     31 
     32 let fileURL;
     33 
     34 function createIndexedDB(host, originAttributes) {
     35  let uri = Services.io.newURI("https://" + host);
     36  let principal = Services.scriptSecurityManager.createContentPrincipal(
     37    uri,
     38    originAttributes
     39  );
     40  return SiteDataTestUtils.addToIndexedDB(principal.origin);
     41 }
     42 
     43 function checkIndexedDB(host, originAttributes) {
     44  return new Promise(resolve => {
     45    let data = true;
     46    let uri = Services.io.newURI("https://" + host);
     47    let principal = Services.scriptSecurityManager.createContentPrincipal(
     48      uri,
     49      originAttributes
     50    );
     51    let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
     52    request.onupgradeneeded = function () {
     53      data = false;
     54    };
     55    request.onsuccess = function () {
     56      resolve(data);
     57    };
     58  });
     59 }
     60 
     61 function createHostCookie(host, originAttributes) {
     62  const cv = Services.cookies.add(
     63    host,
     64    "/test",
     65    "foo",
     66    "bar",
     67    false,
     68    false,
     69    false,
     70    Date.now() + 24000 * 60 * 60,
     71    originAttributes,
     72    Ci.nsICookie.SAMESITE_UNSET,
     73    Ci.nsICookie.SCHEME_HTTPS
     74  );
     75  is(cv.result, Ci.nsICookieValidation.eOK, "Valid cookie");
     76 }
     77 
     78 function createDomainCookie(host, originAttributes) {
     79  const cv = Services.cookies.add(
     80    "." + host,
     81    "/test",
     82    "foo",
     83    "bar",
     84    false,
     85    false,
     86    false,
     87    Date.now() + 24000 * 60 * 60,
     88    originAttributes,
     89    Ci.nsICookie.SAMESITE_UNSET,
     90    Ci.nsICookie.SCHEME_HTTPS
     91  );
     92  is(cv.result, Ci.nsICookieValidation.eOK, "Valid cookie");
     93 }
     94 
     95 function checkCookie(host, originAttributes) {
     96  for (let cookie of Services.cookies.cookies) {
     97    if (
     98      ChromeUtils.isOriginAttributesEqual(
     99        originAttributes,
    100        cookie.originAttributes
    101      ) &&
    102      cookie.host.includes(host)
    103    ) {
    104      return true;
    105    }
    106  }
    107  return false;
    108 }
    109 
    110 async function deleteOnShutdown(opt) {
    111  // Let's clean up all the data.
    112  await new Promise(resolve => {
    113    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
    114  });
    115 
    116  await SpecialPowers.pushPrefEnv({
    117    set: [
    118      ["privacy.sanitize.sanitizeOnShutdown", opt.sanitize],
    119      ["privacy.clearOnShutdown.cookies", opt.sanitize],
    120      ["privacy.clearOnShutdown.offlineApps", opt.sanitize],
    121      ["browser.sanitizer.loglevel", "All"],
    122    ],
    123  });
    124 
    125  // Custom permission without considering OriginAttributes
    126  if (opt.cookiePermission !== undefined) {
    127    let uri = Services.io.newURI("https://www.example.com");
    128    PermissionTestUtils.add(uri, "cookie", opt.cookiePermission);
    129  }
    130 
    131  // Let's create a tab with some data.
    132  await opt.createData(
    133    (opt.fullHost ? "www." : "") + "example.org",
    134    opt.originAttributes
    135  );
    136  ok(
    137    await opt.checkData(
    138      (opt.fullHost ? "www." : "") + "example.org",
    139      opt.originAttributes
    140    ),
    141    "We have data for www.example.org"
    142  );
    143  await opt.createData(
    144    (opt.fullHost ? "www." : "") + "example.com",
    145    opt.originAttributes
    146  );
    147  ok(
    148    await opt.checkData(
    149      (opt.fullHost ? "www." : "") + "example.com",
    150      opt.originAttributes
    151    ),
    152    "We have data for www.example.com"
    153  );
    154 
    155  // Cleaning up.
    156  await Sanitizer.runSanitizeOnShutdown();
    157 
    158  // All gone!
    159  is(
    160    !!(await opt.checkData(
    161      (opt.fullHost ? "www." : "") + "example.org",
    162      opt.originAttributes
    163    )),
    164    opt.expectedForOrg,
    165    "Do we have data for www.example.org?"
    166  );
    167  is(
    168    !!(await opt.checkData(
    169      (opt.fullHost ? "www." : "") + "example.com",
    170      opt.originAttributes
    171    )),
    172    opt.expectedForCom,
    173    "Do we have data for www.example.com?"
    174  );
    175 
    176  // Clean up.
    177  await Sanitizer.sanitize(["cookies", "offlineApps"]);
    178 
    179  if (opt.cookiePermission !== undefined) {
    180    let uri = Services.io.newURI("https://www.example.com");
    181    PermissionTestUtils.remove(uri, "cookie");
    182  }
    183 }
    184 
    185 function runAllCookiePermissionTests(originAttributes) {
    186  let tests = [
    187    { name: "IDB", createData: createIndexedDB, checkData: checkIndexedDB },
    188    {
    189      name: "Host Cookie",
    190      createData: createHostCookie,
    191      checkData: checkCookie,
    192    },
    193    {
    194      name: "Domain Cookie",
    195      createData: createDomainCookie,
    196      checkData: checkCookie,
    197    },
    198  ];
    199 
    200  // Delete all, no custom permission, data in example.com, cookie permission set
    201  // for www.example.com
    202  tests.forEach(methods => {
    203    add_task(async function deleteStorageOnShutdown() {
    204      info(
    205        methods.name +
    206          ": Delete all, no custom permission, data in example.com, cookie permission set for www.example.com - OA: " +
    207          originAttributes.name
    208      );
    209      await deleteOnShutdown({
    210        sanitize: true,
    211        createData: methods.createData,
    212        checkData: methods.checkData,
    213        originAttributes: originAttributes.oa,
    214        cookiePermission: undefined,
    215        expectedForOrg: false,
    216        expectedForCom: false,
    217        fullHost: false,
    218      });
    219    });
    220  });
    221 
    222  // Delete all, no custom permission, data in www.example.com, cookie permission
    223  // set for www.example.com
    224  tests.forEach(methods => {
    225    add_task(async function deleteStorageOnShutdown() {
    226      info(
    227        methods.name +
    228          ": Delete all, no custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " +
    229          originAttributes.name
    230      );
    231      await deleteOnShutdown({
    232        sanitize: true,
    233        createData: methods.createData,
    234        checkData: methods.checkData,
    235        originAttributes: originAttributes.oa,
    236        cookiePermission: undefined,
    237        expectedForOrg: false,
    238        expectedForCom: false,
    239        fullHost: true,
    240      });
    241    });
    242  });
    243 
    244  // All is session, but with ALLOW custom permission, data in example.com,
    245  // cookie permission set for www.example.com
    246  tests.forEach(methods => {
    247    add_task(async function deleteStorageWithCustomPermission() {
    248      info(
    249        methods.name +
    250          ": All is session, but with ALLOW custom permission, data in example.com, cookie permission set for www.example.com - OA: " +
    251          originAttributes.name
    252      );
    253      await deleteOnShutdown({
    254        sanitize: true,
    255        createData: methods.createData,
    256        checkData: methods.checkData,
    257        originAttributes: originAttributes.oa,
    258        cookiePermission: Ci.nsICookiePermission.ACCESS_ALLOW,
    259        expectedForOrg: false,
    260        expectedForCom: true,
    261        fullHost: false,
    262      });
    263    });
    264  });
    265 
    266  // All is session, but with ALLOW custom permission, data in www.example.com,
    267  // cookie permission set for www.example.com
    268  tests.forEach(methods => {
    269    add_task(async function deleteStorageWithCustomPermission() {
    270      info(
    271        methods.name +
    272          ": All is session, but with ALLOW custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " +
    273          originAttributes.name
    274      );
    275      await deleteOnShutdown({
    276        sanitize: true,
    277        createData: methods.createData,
    278        checkData: methods.checkData,
    279        originAttributes: originAttributes.oa,
    280        cookiePermission: Ci.nsICookiePermission.ACCESS_ALLOW,
    281        expectedForOrg: false,
    282        expectedForCom: true,
    283        fullHost: true,
    284      });
    285    });
    286  });
    287 
    288  // All is default, but with SESSION custom permission, data in example.com,
    289  // cookie permission set for www.example.com
    290  tests.forEach(methods => {
    291    add_task(async function deleteStorageOnlyCustomPermission() {
    292      info(
    293        methods.name +
    294          ": All is default, but with SESSION custom permission, data in example.com, cookie permission set for www.example.com - OA: " +
    295          originAttributes.name
    296      );
    297      await deleteOnShutdown({
    298        sanitize: false,
    299        createData: methods.createData,
    300        checkData: methods.checkData,
    301        originAttributes: originAttributes.oa,
    302        cookiePermission: Ci.nsICookiePermission.ACCESS_SESSION,
    303        expectedForOrg: true,
    304        // expected data just for example.com when using indexedDB because
    305        // QuotaManager deletes for principal.
    306        expectedForCom: false,
    307        fullHost: false,
    308      });
    309    });
    310  });
    311 
    312  // All is default, but with SESSION custom permission, data in www.example.com,
    313  // cookie permission set for www.example.com
    314  tests.forEach(methods => {
    315    add_task(async function deleteStorageOnlyCustomPermission() {
    316      info(
    317        methods.name +
    318          ": All is default, but with SESSION custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " +
    319          originAttributes.name
    320      );
    321      await deleteOnShutdown({
    322        sanitize: false,
    323        createData: methods.createData,
    324        checkData: methods.checkData,
    325        originAttributes: originAttributes.oa,
    326        cookiePermission: Ci.nsICookiePermission.ACCESS_SESSION,
    327        expectedForOrg: true,
    328        expectedForCom: false,
    329        fullHost: true,
    330      });
    331    });
    332  });
    333 
    334  // Session mode, but with unsupported custom permission, data in
    335  // www.example.com, cookie permission set for www.example.com
    336  tests.forEach(methods => {
    337    add_task(async function deleteStorageOnlyCustomPermission() {
    338      info(
    339        methods.name +
    340          ": All is session only, but with unsupported custom custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " +
    341          originAttributes.name
    342      );
    343      await deleteOnShutdown({
    344        sanitize: true,
    345        createData: methods.createData,
    346        checkData: methods.checkData,
    347        originAttributes: originAttributes.oa,
    348        cookiePermission: 123, // invalid cookie permission
    349        expectedForOrg: false,
    350        expectedForCom: false,
    351        fullHost: true,
    352      });
    353    });
    354  });
    355 }
    356 
    357 function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) {
    358  return new Promise(resolve => {
    359    let finalPrefPaneLoaded = TestUtils.topicObserved(
    360      "sync-pane-loaded",
    361      () => true
    362    );
    363    gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
    364    openPreferences(aPane);
    365    let newTabBrowser = gBrowser.selectedBrowser;
    366 
    367    newTabBrowser.addEventListener(
    368      "Initialized",
    369      function () {
    370        newTabBrowser.contentWindow.addEventListener(
    371          "load",
    372          async function () {
    373            let win = gBrowser.contentWindow;
    374            let selectedPane = win.history.state;
    375            await finalPrefPaneLoaded;
    376            if (!aOptions || !aOptions.leaveOpen) {
    377              gBrowser.removeCurrentTab();
    378            }
    379            resolve({ selectedPane });
    380          },
    381          { once: true }
    382        );
    383      },
    384      { capture: true, once: true }
    385    );
    386  });
    387 }
    388 
    389 async function createDummyDataForHost(host) {
    390  let origin = "https://" + host;
    391  let dummySWURL =
    392    getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) +
    393    "dummy.js";
    394 
    395  await SiteDataTestUtils.addToIndexedDB(origin);
    396  await SiteDataTestUtils.addServiceWorker({ win: window, path: dummySWURL });
    397 }
    398 
    399 /**
    400 * Helper function to create file URL to open
    401 *
    402 * @returns {object} a file URL
    403 */
    404 function createFileURL() {
    405  if (!fileURL) {
    406    let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
    407    file.append("foo.txt");
    408    file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
    409 
    410    fileURL = Services.io.newFileURI(file);
    411  }
    412  return fileURL;
    413 }
    414 
    415 /**
    416 * Removes all history visits, downloads, and form entries.
    417 */
    418 async function blankSlate() {
    419  let publicList = await Downloads.getList(Downloads.PUBLIC);
    420  let downloads = await publicList.getAll();
    421  for (let download of downloads) {
    422    await publicList.remove(download);
    423    await download.finalize(true);
    424  }
    425 
    426  await FormHistory.update({ op: "remove" });
    427  await PlacesUtils.history.clear();
    428 }
    429 
    430 /**
    431 * Adds multiple downloads to the PUBLIC download list
    432 */
    433 async function addToDownloadList() {
    434  const url = createFileURL();
    435  const downloadsList = await Downloads.getList(Downloads.PUBLIC);
    436  let timeOptions = [1, 2, 4, 24, 128, 128];
    437  let buffer = 100000;
    438 
    439  for (let i = 0; i < timeOptions.length; i++) {
    440    let timeDownloaded = 60 * kMsecPerMin * timeOptions[i];
    441    if (timeOptions[i] === 24) {
    442      timeDownloaded = today;
    443    }
    444 
    445    let download = await Downloads.createDownload({
    446      source: { url: url.spec, isPrivate: false },
    447      target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path },
    448      startTime: {
    449        getTime: _ => {
    450          return nowMSec - timeDownloaded + buffer;
    451        },
    452      },
    453    });
    454 
    455    Assert.ok(!!download);
    456    downloadsList.add(download);
    457  }
    458  let items = await downloadsList.getAll();
    459  Assert.equal(items.length, 6, "Items were added to the list");
    460 }
    461 
    462 async function addToSiteUsage() {
    463  // Fill indexedDB with test data.
    464  // Don't wait for the page to load, to register the content event handler as quickly as possible.
    465  // If this test goes intermittent, we might have to tell the page to wait longer before
    466  // firing the event.
    467  BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_QUOTA_USAGE_URL, false);
    468  await BrowserTestUtils.waitForContentEvent(
    469    gBrowser.selectedBrowser,
    470    "test-indexedDB-done",
    471    false,
    472    null,
    473    true
    474  );
    475  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    476 
    477  let siteLastAccessed = [1, 2, 4, 24];
    478 
    479  let staticUsage = 4096 * 6;
    480  // Add a time buffer so the site access falls within the time range
    481  const buffer = 10000;
    482 
    483  // Change lastAccessed of sites
    484  for (let index = 0; index < siteLastAccessed.length; index++) {
    485    let lastAccessedTime = 60 * kMsecPerMin * siteLastAccessed[index];
    486    if (siteLastAccessed[index] === 24) {
    487      lastAccessedTime = today;
    488    }
    489 
    490    let site = SiteDataManager._testInsertSite(SITE_ORIGINS[index], {
    491      quotaUsage: staticUsage,
    492      lastAccessed: (nowMSec - lastAccessedTime + buffer) * 1000,
    493    });
    494    Assert.ok(site, "Site added successfully");
    495  }
    496 }
    497 
    498 function promiseSanitizationComplete() {
    499  return TestUtils.topicObserved("sanitizer-sanitization-complete");
    500 }
    501 
    502 function settingsRedesignHistoryEnabled() {
    503  return (
    504    Services.prefs.getBoolPref(
    505      "browser.settings-redesign.history2.enabled",
    506      false
    507    ) || Services.prefs.getBoolPref("browser.settings-redesign.enabled", false)
    508  );
    509 }
    510 
    511 /**
    512 * This wraps the dialog and provides some convenience methods for interacting
    513 * with it.
    514 *
    515 * @param {Window} browserWin (optional)
    516 *        The browser window that the dialog is expected to open in. If not
    517 *        supplied, the initial browser window of the test run is used.
    518 * @param {object} {mode, checkingDataSizes}
    519 *        mode: context to open the dialog in
    520 *          One of
    521 *            clear on shutdown settings context ("clearOnShutdown"),
    522 *            clear site data settings context ("clearSiteData"),
    523 *            clear history context ("clearHistory"),
    524 *            browser context ("browser")
    525 *          "browser" by default
    526 *        checkingDataSizes: boolean check if we should wait for the data sizes
    527 *          to load
    528 */
    529 function ClearHistoryDialogHelper({
    530  mode = "browser",
    531  checkingDataSizes = false,
    532 } = {}) {
    533  this._browserWin = window;
    534  this.win = null;
    535  this._mode = mode;
    536  this._checkingDataSizes = checkingDataSizes;
    537  this.promiseClosed = new Promise(resolve => {
    538    this._resolveClosed = resolve;
    539  });
    540 }
    541 
    542 ClearHistoryDialogHelper.prototype = {
    543  /**
    544   * "Presses" the dialog's OK button.
    545   */
    546  acceptDialog() {
    547    let dialogEl = this.win.document.querySelector("dialog");
    548    is(
    549      dialogEl.getButton("accept").disabled,
    550      false,
    551      "Dialog's OK button should not be disabled"
    552    );
    553    dialogEl.acceptDialog();
    554  },
    555 
    556  /**
    557   * "Presses" the dialog's Cancel button.
    558   */
    559  cancelDialog() {
    560    this.win.document.querySelector("dialog").cancelDialog();
    561  },
    562 
    563  /**
    564   * (Un)checks a history scope checkbox (browser & download history,
    565   * form history, etc.).
    566   *
    567   * @param aPrefName
    568   *        The final portion of the checkbox's privacy.cpd.* preference name
    569   * @param aCheckState
    570   *        True if the checkbox should be checked, false otherwise
    571   */
    572  checkPrefCheckbox(aPrefName, aCheckState) {
    573    var cb = this.win.document.querySelectorAll(
    574      "checkbox[id='" + aPrefName + "']"
    575    );
    576    is(cb.length, 1, "found checkbox for " + aPrefName + " id");
    577    if (cb[0].checked != aCheckState) {
    578      cb[0].click();
    579    }
    580  },
    581 
    582  /**
    583   * @param {string} aCheckboxId
    584   *        The checkbox id name
    585   * @param {boolean} aCheckState
    586   *        True if the checkbox should be checked, false otherwise
    587   */
    588  validateCheckbox(aCheckboxId, aCheckState) {
    589    let cb = this.win.document.querySelectorAll(
    590      "checkbox[id='" + aCheckboxId + "']"
    591    );
    592    is(cb.length, 1, `found checkbox for id=${aCheckboxId}`);
    593    is(
    594      cb[0].checked,
    595      aCheckState,
    596      `checkbox for ${aCheckboxId} is ${aCheckState}`
    597    );
    598  },
    599 
    600  /**
    601   * Makes sure all the checkboxes are checked.
    602   */
    603  _checkAllCheckboxesCustom(check) {
    604    var cb = this.win.document.querySelectorAll(".clearingItemCheckbox");
    605    ok(cb.length, "found checkboxes for ids");
    606    for (var i = 0; i < cb.length; ++i) {
    607      if (cb[i].checked != check) {
    608        cb[i].click();
    609      }
    610    }
    611  },
    612 
    613  checkAllCheckboxes() {
    614    this._checkAllCheckboxesCustom(true);
    615  },
    616 
    617  uncheckAllCheckboxes() {
    618    this._checkAllCheckboxesCustom(false);
    619  },
    620 
    621  /**
    622   * @return The dialog's duration dropdown
    623   */
    624  getDurationDropdown() {
    625    return this.win.document.getElementById("sanitizeDurationChoice");
    626  },
    627 
    628  /**
    629   * @return The clear-everything warning box
    630   */
    631  getWarningPanel() {
    632    return this.win.document.getElementById("sanitizeEverythingWarningBox");
    633  },
    634 
    635  /**
    636   * @return True if the "Everything" warning panel is visible (as opposed to
    637   *         the tree)
    638   */
    639  isWarningPanelVisible() {
    640    return !this.getWarningPanel().hidden;
    641  },
    642 
    643  /**
    644   * Opens the clear recent history dialog.  Before calling this, set
    645   * this.onload to a function to execute onload.  It should close the dialog
    646   * when done so that the tests may continue.  Set this.onunload to a function
    647   * to execute onunload.  this.onunload is optional. If it returns true, the
    648   * caller is expected to call promiseAsyncUpdates at some point; if false is
    649   * returned, promiseAsyncUpdates is called automatically.
    650   */
    651  async open() {
    652    let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
    653      null,
    654      "chrome://browser/content/sanitize_v2.xhtml",
    655      {
    656        isSubDialog: true,
    657      }
    658    );
    659 
    660    // We want to simulate opening the dialog inside preferences for clear history
    661    // and clear site data
    662    if (this._mode != "browser") {
    663      if (this._mode == "clearOnShutdown" && settingsRedesignHistoryEnabled()) {
    664        await openPreferencesViaOpenPreferencesAPI("history", {
    665          leaveOpen: true,
    666        });
    667      } else {
    668        await openPreferencesViaOpenPreferencesAPI("privacy", {
    669          leaveOpen: true,
    670        });
    671      }
    672      let tabWindow = gBrowser.selectedBrowser.contentWindow;
    673      let clearDialogOpenButtonId = this._mode + "Button";
    674      // the id for clear on shutdown is of a different format
    675      if (this._mode == "clearOnShutdown") {
    676        // set always clear to true to enable the clear on shutdown dialog
    677        let enableSettingsCheckbox =
    678          tabWindow.document.getElementById("alwaysClear");
    679        if (!enableSettingsCheckbox.checked) {
    680          enableSettingsCheckbox.click();
    681        }
    682        clearDialogOpenButtonId = "clearDataSettings";
    683      }
    684      // open dialog
    685      // Wait a tick for the button to be initialized.œ
    686      await new Promise(resolve => requestAnimationFrame(resolve));
    687      tabWindow.document.getElementById(clearDialogOpenButtonId).click();
    688    }
    689    // We open the dialog in the chrome context in other cases
    690    else {
    691      executeSoon(() => {
    692        Sanitizer.showUI(this._browserWin, this._mode);
    693      });
    694    }
    695 
    696    this.win = await dialogPromise;
    697    this.win.addEventListener(
    698      "load",
    699      () => {
    700        // Run onload on next tick so that gSanitizePromptDialog.init can run first.
    701        executeSoon(async () => {
    702          if (this._checkingDataSizes) {
    703            // we wait for the data sizes to load to avoid async errors when validating sizes
    704            await this.win.gSanitizePromptDialog
    705              .dataSizesFinishedUpdatingPromise;
    706          }
    707          this.onload();
    708        });
    709      },
    710      { once: true }
    711    );
    712    this.win.addEventListener(
    713      "unload",
    714      () => {
    715        // Some exceptions that reach here don't reach the test harness, but
    716        // ok()/is() do...
    717        (async () => {
    718          if (this.onunload) {
    719            await this.onunload();
    720          }
    721          if (this._mode != "browser") {
    722            BrowserTestUtils.removeTab(gBrowser.selectedTab);
    723          }
    724          await PlacesTestUtils.promiseAsyncUpdates();
    725          this._resolveClosed();
    726          this.win = null;
    727        })();
    728      },
    729      { once: true }
    730    );
    731  },
    732 
    733  /**
    734   * Selects a duration in the duration dropdown.
    735   *
    736   * @param aDurVal
    737   *        One of the Sanitizer.TIMESPAN_* values
    738   */
    739  selectDuration(aDurVal) {
    740    this.getDurationDropdown().value = aDurVal;
    741    if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
    742      is(
    743        this.isWarningPanelVisible(),
    744        true,
    745        "Warning panel should be visible for TIMESPAN_EVERYTHING"
    746      );
    747    } else {
    748      is(
    749        this.isWarningPanelVisible(),
    750        false,
    751        "Warning panel should not be visible for non-TIMESPAN_EVERYTHING"
    752      );
    753    }
    754  },
    755 };