tor-browser

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

browser_localSearchShortcuts.js (10391B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 /**
      5 * Checks the local shortcut rows in the engines list of the search pane.
      6 */
      7 
      8 "use strict";
      9 
     10 ChromeUtils.defineESModuleGetters(this, {
     11  UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
     12  UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs",
     13  UrlbarTokenizer:
     14    "moz-src:///browser/components/urlbar/UrlbarTokenizer.sys.mjs",
     15 });
     16 
     17 let gTree;
     18 const isRestrictKeywordsFeatureOn = () =>
     19  UrlbarPrefs.getScotchBonnetPref("searchRestrictKeywords.featureGate");
     20 
     21 add_setup(async function () {
     22  await SpecialPowers.pushPrefEnv({
     23    set: [["browser.urlbar.scotchBonnet.enableOverride", true]],
     24  });
     25  let prefs = await openPreferencesViaOpenPreferencesAPI("search", {
     26    leaveOpen: true,
     27  });
     28  registerCleanupFunction(() => {
     29    BrowserTestUtils.removeTab(gBrowser.selectedTab);
     30  });
     31 
     32  Assert.equal(
     33    prefs.selectedPane,
     34    "paneSearch",
     35    "Sanity check: Search pane is selected by default"
     36  );
     37 
     38  gTree = gBrowser.contentDocument.querySelector("#engineList");
     39  gTree.scrollIntoView();
     40  gTree.focus();
     41 });
     42 
     43 // The rows should be visible and checked by default.
     44 add_task(async function visible() {
     45  await checkRowVisibility(true);
     46  await forEachLocalShortcutRow(async row => {
     47    Assert.equal(
     48      gTree.view.getCellValue(row, gTree.columns.getNamedColumn("engineShown")),
     49      "true",
     50      "Row is checked initially"
     51    );
     52  });
     53 });
     54 
     55 // Toggling the browser.urlbar.shortcuts.* prefs should toggle the corresponding
     56 // checkboxes in the rows.
     57 add_task(async function syncFromPrefs() {
     58  let col = gTree.columns.getNamedColumn("engineShown");
     59  await forEachLocalShortcutRow(async (row, shortcut) => {
     60    Assert.equal(
     61      gTree.view.getCellValue(row, col),
     62      "true",
     63      "Row is checked initially"
     64    );
     65    await SpecialPowers.pushPrefEnv({
     66      set: [[getUrlbarPrefName(shortcut.pref), false]],
     67    });
     68    Assert.equal(
     69      gTree.view.getCellValue(row, col),
     70      "false",
     71      "Row is unchecked after disabling pref"
     72    );
     73    await SpecialPowers.popPrefEnv();
     74    Assert.equal(
     75      gTree.view.getCellValue(row, col),
     76      "true",
     77      "Row is checked after re-enabling pref"
     78    );
     79  });
     80 });
     81 
     82 // Pressing the space key while a row is selected should toggle its checkbox
     83 // and pref.
     84 add_task(async function syncToPrefs_spaceKey() {
     85  let col = gTree.columns.getNamedColumn("engineShown");
     86  await forEachLocalShortcutRow(async (row, shortcut) => {
     87    Assert.ok(
     88      UrlbarPrefs.get(shortcut.pref),
     89      "Sanity check: Pref is enabled initially"
     90    );
     91    Assert.equal(
     92      gTree.view.getCellValue(row, col),
     93      "true",
     94      "Row is checked initially"
     95    );
     96    gTree.view.selection.select(row);
     97    EventUtils.synthesizeKey(" ", {}, gTree.ownerGlobal);
     98    Assert.ok(
     99      !UrlbarPrefs.get(shortcut.pref),
    100      "Pref is disabled after pressing space key"
    101    );
    102    Assert.equal(
    103      gTree.view.getCellValue(row, col),
    104      "false",
    105      "Row is unchecked after pressing space key"
    106    );
    107    Services.prefs.clearUserPref(getUrlbarPrefName(shortcut.pref));
    108  });
    109 });
    110 
    111 // Clicking the checkbox in a local shortcut row should toggle the checkbox and
    112 // pref.
    113 add_task(async function syncToPrefs_click() {
    114  let col = gTree.columns.getNamedColumn("engineShown");
    115  await forEachLocalShortcutRow(async (row, shortcut) => {
    116    Assert.ok(
    117      UrlbarPrefs.get(shortcut.pref),
    118      "Sanity check: Pref is enabled initially"
    119    );
    120    Assert.equal(
    121      gTree.view.getCellValue(row, col),
    122      "true",
    123      "Row is checked initially"
    124    );
    125 
    126    let rect = gTree.getCoordsForCellItem(row, col, "cell");
    127    let x = rect.x + rect.width / 2;
    128    let y = rect.y + rect.height / 2;
    129    EventUtils.synthesizeMouse(gTree.body, x, y, {}, gTree.ownerGlobal);
    130 
    131    Assert.ok(
    132      !UrlbarPrefs.get(shortcut.pref),
    133      "Pref is disabled after clicking checkbox"
    134    );
    135    Assert.equal(
    136      gTree.view.getCellValue(row, col),
    137      "false",
    138      "Row is unchecked after clicking checkbox"
    139    );
    140    Services.prefs.clearUserPref(getUrlbarPrefName(shortcut.pref));
    141  });
    142 });
    143 
    144 // The keyword column should not be editable according to isEditable().
    145 add_task(async function keywordNotEditable_isEditable() {
    146  await forEachLocalShortcutRow(async row => {
    147    Assert.ok(
    148      !gTree.view.isEditable(
    149        row,
    150        gTree.columns.getNamedColumn("engineKeyword")
    151      ),
    152      "Keyword column is not editable"
    153    );
    154  });
    155 });
    156 
    157 // Pressing the enter key while a row is selected shouldn't allow the keyword to
    158 // be edited.
    159 add_task(async function keywordNotEditable_enterKey() {
    160  let col = gTree.columns.getNamedColumn("engineKeyword");
    161  await forEachLocalShortcutRow(async (row, shortcut) => {
    162    Assert.ok(
    163      shortcut.restrict,
    164      "Sanity check: Shortcut restriction char is non-empty"
    165    );
    166 
    167    let tokenToKeywords = await UrlbarTokenizer.getL10nRestrictKeywords();
    168    let keywords = tokenToKeywords
    169      .get(shortcut.restrict)
    170      .map(keyword => `@${keyword.toLowerCase()}`)
    171      .join(", ");
    172 
    173    Assert.equal(
    174      gTree.view.getCellText(row, col),
    175      isRestrictKeywordsFeatureOn()
    176        ? `${keywords}, ${shortcut.restrict}`
    177        : shortcut.restrict,
    178      isRestrictKeywordsFeatureOn()
    179        ? "Sanity check: Keyword column has correct restriction keyword and char initially"
    180        : "Sanity check: Keyword column has correct restriction char initially"
    181    );
    182 
    183    gTree.view.selection.select(row);
    184    EventUtils.synthesizeKey("KEY_Enter", {}, gTree.ownerGlobal);
    185    EventUtils.sendString("newkeyword");
    186    EventUtils.synthesizeKey("KEY_Enter", {}, gTree.ownerGlobal);
    187 
    188    // Wait a moment to allow for any possible asynchronicity.
    189    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    190    await new Promise(r => setTimeout(r, 500));
    191 
    192    Assert.equal(
    193      gTree.view.getCellText(row, col),
    194      isRestrictKeywordsFeatureOn()
    195        ? `${keywords}, ${shortcut.restrict}`
    196        : shortcut.restrict,
    197      isRestrictKeywordsFeatureOn()
    198        ? "Keyword column is still restriction keyword and char"
    199        : "Keyword column is still restriction char"
    200    );
    201  });
    202 });
    203 
    204 // Double-clicking the keyword column shouldn't allow the keyword to be edited.
    205 add_task(async function keywordNotEditable_click() {
    206  let col = gTree.columns.getNamedColumn("engineKeyword");
    207  await forEachLocalShortcutRow(async (row, shortcut) => {
    208    let tokenToKeywords = await UrlbarTokenizer.getL10nRestrictKeywords();
    209    let keywords = tokenToKeywords
    210      .get(shortcut.restrict)
    211      .map(keyword => `@${keyword.toLowerCase()}`)
    212      .join(", ");
    213 
    214    Assert.ok(
    215      shortcut.restrict,
    216      "Sanity check: Shortcut restriction char is non-empty"
    217    );
    218    Assert.equal(
    219      gTree.view.getCellText(row, col),
    220      isRestrictKeywordsFeatureOn()
    221        ? `${keywords}, ${shortcut.restrict}`
    222        : shortcut.restrict,
    223      isRestrictKeywordsFeatureOn()
    224        ? "Sanity check: Keyword column has correct restriction keyword and char initially"
    225        : "Sanity check: Keyword column has correct restriction char initially"
    226    );
    227 
    228    let rect = gTree.getCoordsForCellItem(row, col, "text");
    229    let x = rect.x + rect.width / 2;
    230    let y = rect.y + rect.height / 2;
    231 
    232    let promise = BrowserTestUtils.waitForEvent(gTree, "dblclick");
    233 
    234    // Click once to select the row.
    235    EventUtils.synthesizeMouse(
    236      gTree.body,
    237      x,
    238      y,
    239      { clickCount: 1 },
    240      gTree.ownerGlobal
    241    );
    242 
    243    // Now double-click the keyword column.
    244    EventUtils.synthesizeMouse(
    245      gTree.body,
    246      x,
    247      y,
    248      { clickCount: 2 },
    249      gTree.ownerGlobal
    250    );
    251 
    252    await promise;
    253 
    254    EventUtils.sendString("newkeyword");
    255    EventUtils.synthesizeKey("KEY_Enter", {}, gTree.ownerGlobal);
    256 
    257    // Wait a moment to allow for any possible asynchronicity.
    258    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    259    await new Promise(r => setTimeout(r, 500));
    260 
    261    Assert.equal(
    262      gTree.view.getCellText(row, col),
    263      isRestrictKeywordsFeatureOn()
    264        ? `${keywords}, ${shortcut.restrict}`
    265        : shortcut.restrict,
    266      isRestrictKeywordsFeatureOn()
    267        ? "Keyword column is still restriction keyword and char"
    268        : "Keyword column is still restriction char"
    269    );
    270  });
    271 });
    272 
    273 /**
    274 * Asserts that the engine and local shortcut rows are present in the tree.
    275 */
    276 async function checkRowVisibility() {
    277  let engines = await Services.search.getVisibleEngines();
    278 
    279  Assert.equal(
    280    gTree.view.rowCount,
    281    engines.length + UrlbarUtils.LOCAL_SEARCH_MODES.length,
    282    "Expected number of tree rows"
    283  );
    284 
    285  // Check the engine rows.
    286  for (let row = 0; row < engines.length; row++) {
    287    let engine = engines[row];
    288    let text = gTree.view.getCellText(
    289      row,
    290      gTree.columns.getNamedColumn("engineName")
    291    );
    292    Assert.equal(
    293      text,
    294      engine.name,
    295      `Sanity check: Tree row ${row} has expected engine name`
    296    );
    297  }
    298 
    299  // Check the shortcut rows.
    300  await forEachLocalShortcutRow(async (row, shortcut) => {
    301    let text = gTree.view.getCellText(
    302      row,
    303      gTree.columns.getNamedColumn("engineName")
    304    );
    305    let name = UrlbarUtils.getResultSourceName(shortcut.source);
    306    let l10nName = await gTree.ownerDocument.l10n.formatValue(
    307      `urlbar-search-mode-${name}`
    308    );
    309    Assert.ok(l10nName, "Sanity check: l10n name is non-empty");
    310    Assert.equal(text, l10nName, `Tree row ${row} has expected shortcut name`);
    311  });
    312 }
    313 
    314 /**
    315 * Calls a callback for each local shortcut row in the tree.
    316 *
    317 * @param {function} callback
    318 *   Called for each local shortcut row like: callback(rowIndex, shortcutObject)
    319 */
    320 async function forEachLocalShortcutRow(callback) {
    321  let engines = await Services.search.getVisibleEngines();
    322  for (let i = 0; i < UrlbarUtils.LOCAL_SEARCH_MODES.length; i++) {
    323    let shortcut = UrlbarUtils.LOCAL_SEARCH_MODES[i];
    324    let row = engines.length + i;
    325    await callback(row, shortcut);
    326  }
    327 }
    328 
    329 /**
    330 * Prepends the `browser.urlbar.` branch to the given relative pref.
    331 *
    332 * @param {string} relativePref
    333 *   A pref name relative to the `browser.urlbar.`.
    334 * @returns {string}
    335 *   The full pref name with `browser.urlbar.` prepended.
    336 */
    337 function getUrlbarPrefName(relativePref) {
    338  return `browser.urlbar.${relativePref}`;
    339 }