tor-browser

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

browser_styleeditor_autocomplete.js (8155B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 "use strict";
      4 
      5 // Test that autocompletion works as expected.
      6 
      7 const TESTCASE_URI = TEST_BASE_HTTP + "autocomplete.html";
      8 const MAX_SUGGESTIONS = 15;
      9 
     10 // Test cases to test that autocompletion works correctly when enabled.
     11 // Format:
     12 // [
     13 //   key,
     14 //   {
     15 //     total: Number of suggestions in the popup (-1 if popup is closed),
     16 //     current: Index of selected suggestion,
     17 //     inserted: 1 to check whether the selected suggestion is inserted into the
     18 //               editor or not,
     19 //     entered: 1 if the suggestion is inserted and finalized
     20 //   }
     21 // ]
     22 
     23 function getTestCases(cssProperties) {
     24  const keywords = getCSSKeywords(cssProperties);
     25  const getSuggestionNumberFor = suggestionNumberGetter(keywords);
     26 
     27  return [
     28    ["VK_RIGHT"],
     29    ["VK_RIGHT"],
     30    ["VK_RIGHT"],
     31    ["VK_RIGHT"],
     32    ["Ctrl+Space", { total: 1, current: 0 }],
     33    ["VK_LEFT"],
     34    ["VK_RIGHT"],
     35    ["VK_DOWN"],
     36    ["VK_RIGHT"],
     37    ["VK_RIGHT"],
     38    ["VK_RIGHT"],
     39    ["Ctrl+Space", { total: getSuggestionNumberFor("font"), current: 0 }],
     40    ["VK_END"],
     41    ["VK_RETURN"],
     42    ["b", { total: getSuggestionNumberFor("b"), current: 0 }],
     43    ["a", { total: getSuggestionNumberFor("ba"), current: 0 }],
     44    [
     45      "VK_DOWN",
     46      { total: getSuggestionNumberFor("ba"), current: 0, inserted: 1 },
     47    ],
     48    [
     49      "VK_DOWN",
     50      { total: getSuggestionNumberFor("ba"), current: 1, inserted: 1 },
     51    ],
     52    [
     53      "VK_TAB",
     54      { total: getSuggestionNumberFor("ba"), current: 2, inserted: 1 },
     55    ],
     56    ["VK_RETURN", { current: 2, inserted: 1, entered: 1 }],
     57    ["b", { total: getSuggestionNumberFor("background", "b"), current: 0 }],
     58    ["l", { total: getSuggestionNumberFor("background", "bl"), current: 0 }],
     59    [
     60      "VK_TAB",
     61      {
     62        total: getSuggestionNumberFor("background", "bl"),
     63        current: 0,
     64        inserted: 1,
     65      },
     66    ],
     67    [
     68      "VK_DOWN",
     69      {
     70        total: getSuggestionNumberFor("background", "bl"),
     71        current: 1,
     72        inserted: 1,
     73      },
     74    ],
     75    [
     76      "VK_UP",
     77      {
     78        total: getSuggestionNumberFor("background", "bl"),
     79        current: 0,
     80        inserted: 1,
     81      },
     82    ],
     83    [
     84      "VK_TAB",
     85      {
     86        total: getSuggestionNumberFor("background", "bl"),
     87        current: 1,
     88        inserted: 1,
     89      },
     90    ],
     91    [
     92      "VK_TAB",
     93      {
     94        total: getSuggestionNumberFor("background", "bl"),
     95        current: 2,
     96        inserted: 1,
     97      },
     98    ],
     99    [";"],
    100    ["VK_RETURN"],
    101    ["c", { total: getSuggestionNumberFor("c"), current: 0 }],
    102    ["o", { total: getSuggestionNumberFor("co"), current: 0 }],
    103    ["VK_RETURN", { current: 0, inserted: 1 }],
    104    ["r", { total: getSuggestionNumberFor("color", "r"), current: 0 }],
    105    ["VK_RETURN", { current: 0, inserted: 1 }],
    106    [";"],
    107    ["VK_LEFT"],
    108    ["VK_RIGHT"],
    109    ["VK_DOWN"],
    110    ["VK_RETURN"],
    111    ["b", { total: 2, current: 0 }],
    112    ["u", { total: 1, current: 0 }],
    113    ["VK_RETURN", { current: 0, inserted: 1 }],
    114    ["{"],
    115    ["VK_HOME"],
    116    ["VK_DOWN"],
    117    ["VK_DOWN"],
    118    ["VK_RIGHT"],
    119    ["VK_RIGHT"],
    120    ["VK_RIGHT"],
    121    ["VK_RIGHT"],
    122    ["VK_RIGHT"],
    123    ["VK_RIGHT"],
    124    ["VK_RIGHT"],
    125    ["VK_RIGHT"],
    126    ["VK_RIGHT"],
    127    ["VK_RIGHT"],
    128    ["Ctrl+Space", { total: 1, current: 0 }],
    129  ];
    130 }
    131 
    132 add_task(async function () {
    133  // We try to type "background" above, so backdrop-filter enabledness affects
    134  // the expectations. Instead of branching on the test set the pref to true
    135  // here as that is the end state, and it doesn't interact with the test in
    136  // other ways.
    137  await SpecialPowers.pushPrefEnv({
    138    set: [["layout.css.backdrop-filter.enabled", true]],
    139  });
    140  const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI);
    141  const { cssProperties } = ui;
    142  const testCases = getTestCases(cssProperties);
    143 
    144  await ui.selectStyleSheet(ui.editors[1].styleSheet);
    145  const editor = await ui.editors[1].getSourceEditor();
    146 
    147  const sourceEditor = editor.sourceEditor;
    148  const popup = sourceEditor.getAutocompletionPopup();
    149 
    150  await SimpleTest.promiseFocus(panel.panelWindow);
    151 
    152  for (const index in testCases) {
    153    await testState(testCases, index, sourceEditor, popup, panel.panelWindow);
    154    await checkState(testCases, index, sourceEditor, popup);
    155  }
    156 });
    157 
    158 function testState(testCases, index, sourceEditor, popup, panelWindow) {
    159  let [key, details] = testCases[index];
    160  let entered;
    161  if (details) {
    162    entered = details.entered;
    163  }
    164  const mods = {};
    165 
    166  info(
    167    "pressing key " +
    168      key +
    169      " to get result: " +
    170      JSON.stringify(testCases[index]) +
    171      " for index " +
    172      index
    173  );
    174 
    175  let evt = "after-suggest";
    176 
    177  if (key == "Ctrl+Space") {
    178    key = " ";
    179    mods.ctrlKey = true;
    180  } else if (key == "VK_RETURN" && entered) {
    181    evt = "popup-hidden";
    182  } else if (
    183    /(left|right|return|home|end)/gi.test(key) ||
    184    (key == "VK_DOWN" && !popup.isOpen)
    185  ) {
    186    evt = "cursorActivity";
    187  } else if (key == "VK_TAB" || key == "VK_UP" || key == "VK_DOWN") {
    188    evt = "suggestion-entered";
    189  }
    190 
    191  const ready = sourceEditor.once(evt);
    192  EventUtils.synthesizeKey(key, mods, panelWindow);
    193 
    194  return ready;
    195 }
    196 
    197 function checkState(testCases, index, sourceEditor, popup) {
    198  return new Promise(resolve => {
    199    executeSoon(() => {
    200      let [, details] = testCases[index];
    201      details = details || {};
    202      const { total, current, inserted } = details;
    203 
    204      if (total != undefined) {
    205        ok(popup.isOpen, "Popup is open for index " + index);
    206        is(
    207          total,
    208          popup.itemCount,
    209          "Correct total suggestions for index " + index
    210        );
    211        is(
    212          current,
    213          popup.selectedIndex,
    214          "Correct index is selected for index " + index
    215        );
    216        if (inserted) {
    217          const { text } = popup.getItemAtIndex(current);
    218          const { line, ch } = sourceEditor.getCursor();
    219          const lineText = sourceEditor.getText(line);
    220          is(
    221            lineText.substring(ch - text.length, ch),
    222            text,
    223            "Current suggestion from the popup is inserted into the editor."
    224          );
    225        }
    226      } else {
    227        ok(!popup.isOpen, "Popup is closed for index " + index);
    228        if (inserted) {
    229          const { text } = popup.getItemAtIndex(current);
    230          const { line, ch } = sourceEditor.getCursor();
    231          const lineText = sourceEditor.getText(line);
    232          is(
    233            lineText.substring(ch - text.length, ch),
    234            text,
    235            "Current suggestion from the popup is inserted into the editor."
    236          );
    237        }
    238      }
    239      resolve();
    240    });
    241  });
    242 }
    243 
    244 /**
    245 * Returns a list of all property names and a map of property name vs possible
    246 * CSS values provided by the Gecko engine.
    247 *
    248 * @return {object} An object with following properties:
    249 *         - CSSProperties {Array} Array of string containing all the possible
    250 *                         CSS property names.
    251 *         - CSSValues {Object|Map} A map where key is the property name and
    252 *                     value is an array of string containing all the possible
    253 *                     CSS values the property can have.
    254 */
    255 function getCSSKeywords(cssProperties) {
    256  const props = {};
    257  const propNames = cssProperties.getNames();
    258  propNames.forEach(prop => {
    259    props[prop] = cssProperties.getValues(prop).sort();
    260  });
    261  return {
    262    CSSValues: props,
    263    CSSProperties: propNames.sort(),
    264  };
    265 }
    266 
    267 /**
    268 * Returns a function that returns the number of expected suggestions for the given
    269 * property and value. If the value is not null, returns the number of values starting
    270 * with `value`. Returns the number of properties starting with `property` otherwise.
    271 */
    272 function suggestionNumberGetter({ CSSProperties, CSSValues }) {
    273  return (property, value) => {
    274    if (value == null) {
    275      return CSSProperties.filter(prop => prop.startsWith(property)).slice(
    276        0,
    277        MAX_SUGGESTIONS
    278      ).length;
    279    }
    280    return CSSValues[property]
    281      .filter(val => val.startsWith(value))
    282      .slice(0, MAX_SUGGESTIONS).length;
    283  };
    284 }