tor-browser

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

test_js_property_provider.js (23794B)


      1 // Any copyright is dedicated to the Public Domain.
      2 // http://creativecommons.org/publicdomain/zero/1.0/
      3 
      4 "use strict";
      5 const {
      6  fallibleJsPropertyProvider: jsPropertyProvider,
      7 } = require("resource://devtools/shared/webconsole/js-property-provider.js");
      8 
      9 const { addDebuggerToGlobal } = ChromeUtils.importESModule(
     10  "resource://gre/modules/jsdebugger.sys.mjs"
     11 );
     12 addDebuggerToGlobal(globalThis);
     13 
     14 function run_test() {
     15  Services.prefs.setBoolPref(
     16    "security.allow_parent_unrestricted_js_loads",
     17    true
     18  );
     19  registerCleanupFunction(() => {
     20    Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
     21  });
     22 
     23  const testArray = `var testArray = [
     24    {propA: "A"},
     25    {
     26      propB: "B",
     27      propC: [
     28        "D"
     29      ]
     30    },
     31    [
     32      {propE: "E"}
     33    ]
     34  ]`;
     35 
     36  const testObject = 'var testObject = {"propA": [{"propB": "B"}]}';
     37  const testHyphenated = 'var testHyphenated = {"prop-A": "res-A"}';
     38  const testLet = "let foobar = {a: ''}; const blargh = {a: 1};";
     39 
     40  const testGenerators = `
     41  // Test with generator using a named function.
     42  function* genFunc() {
     43    for (let i = 0; i < 10; i++) {
     44      yield i;
     45    }
     46  }
     47  let gen1 = genFunc();
     48  gen1.next();
     49 
     50  // Test with generator using an anonymous function.
     51  let gen2 = (function* () {
     52    for (let i = 0; i < 10; i++) {
     53      yield i;
     54    }
     55  })();`;
     56 
     57  const testGetters = `
     58    var testGetters = {
     59      get x() {
     60        return Object.create(null, Object.getOwnPropertyDescriptors({
     61          hello: "",
     62          world: "",
     63        }));
     64      },
     65      get y() {
     66        return Object.create(null, Object.getOwnPropertyDescriptors({
     67          get y() {
     68            return "plop";
     69          },
     70        }));
     71      }
     72    };
     73  `;
     74 
     75  const testProxies = `
     76    var testSelfPrototypeProxy = new Proxy({
     77      hello: 1
     78    }, {
     79      getPrototypeOf: () => testProxy
     80    });
     81    var testArrayPrototypeProxy = new Proxy({
     82      world: 2
     83    }, {
     84      getPrototypeOf: () => Array.prototype
     85    })
     86  `;
     87 
     88  const sandbox = Cu.Sandbox("http://example.com");
     89  const dbg = new Debugger();
     90  const dbgObject = dbg.addDebuggee(sandbox);
     91  const dbgEnv = dbgObject.asEnvironment();
     92  Cu.evalInSandbox(
     93    `
     94    const hello = Object.create(null, Object.getOwnPropertyDescriptors({world: 1}));
     95    String.prototype.hello = hello;
     96    Number.prototype.hello = hello;
     97    Array.prototype.hello = hello;
     98  `,
     99    sandbox
    100  );
    101  Cu.evalInSandbox(testArray, sandbox);
    102  Cu.evalInSandbox(testObject, sandbox);
    103  Cu.evalInSandbox(testHyphenated, sandbox);
    104  Cu.evalInSandbox(testLet, sandbox);
    105  Cu.evalInSandbox(testGenerators, sandbox);
    106  Cu.evalInSandbox(testGetters, sandbox);
    107  Cu.evalInSandbox(testProxies, sandbox);
    108 
    109  info("Running tests with dbgObject");
    110  runChecks(dbgObject, null, sandbox);
    111 
    112  info("Running tests with dbgEnv");
    113  runChecks(null, dbgEnv, sandbox);
    114 }
    115 
    116 function runChecks(dbgObject, environment, sandbox) {
    117  const propertyProvider = (inputValue, options) =>
    118    jsPropertyProvider({
    119      dbgObject,
    120      environment,
    121      inputValue,
    122      ...options,
    123    });
    124 
    125  info("Test that suggestions are given for 'this'");
    126  let results = propertyProvider("t");
    127  test_has_result(results, "this");
    128 
    129  if (dbgObject != null) {
    130    info("Test that suggestions are given for 'this.'");
    131    results = propertyProvider("this.");
    132    test_has_result(results, "testObject");
    133 
    134    info("Test that suggestions are given for '(this).'");
    135    results = propertyProvider("(this).");
    136    test_has_result(results, "testObject");
    137 
    138    info("Test that suggestions are given for deep 'this' properties access");
    139    results = propertyProvider("(this).testObject.propA.");
    140    test_has_result(results, "shift");
    141 
    142    results = propertyProvider("(this).testObject.propA[");
    143    test_has_result(results, `"shift"`);
    144 
    145    results = propertyProvider("(this)['testObject']['propA'][");
    146    test_has_result(results, `"shift"`);
    147 
    148    results = propertyProvider("(this).testObject['propA'].");
    149    test_has_result(results, "shift");
    150 
    151    info("Test that no suggestions are given for 'this.this'");
    152    results = propertyProvider("this.this");
    153    test_has_no_results(results);
    154  }
    155 
    156  info("Test that suggestions are given for 'globalThis'");
    157  results = propertyProvider("g");
    158  test_has_result(results, "globalThis");
    159 
    160  info("Test that suggestions are given for 'globalThis.'");
    161  results = propertyProvider("globalThis.");
    162  test_has_result(results, "testObject");
    163 
    164  info("Test that suggestions are given for '(globalThis).'");
    165  results = propertyProvider("(globalThis).");
    166  test_has_result(results, "testObject");
    167  test_has_result(results, "Infinity");
    168 
    169  info(
    170    "Test that suggestions are given for deep 'globalThis' properties access"
    171  );
    172  results = propertyProvider("(globalThis).testObject.propA.");
    173  test_has_result(results, "shift");
    174 
    175  results = propertyProvider("(globalThis).testObject.propA[");
    176  test_has_result(results, `"shift"`);
    177 
    178  results = propertyProvider("(globalThis)['testObject']['propA'][");
    179  test_has_result(results, `"shift"`);
    180 
    181  results = propertyProvider("(globalThis).testObject['propA'].");
    182  test_has_result(results, "shift");
    183 
    184  info("Testing lexical scope issues (Bug 1207868)");
    185  results = propertyProvider("foobar");
    186  test_has_result(results, "foobar");
    187 
    188  results = propertyProvider("foobar.");
    189  test_has_result(results, "a");
    190 
    191  results = propertyProvider("blargh");
    192  test_has_result(results, "blargh");
    193 
    194  results = propertyProvider("blargh.");
    195  test_has_result(results, "a");
    196 
    197  info("Test that suggestions are given for 'foo[n]' where n is an integer.");
    198  results = propertyProvider("testArray[0].");
    199  test_has_result(results, "propA");
    200 
    201  info("Test that suggestions are given for multidimensional arrays.");
    202  results = propertyProvider("testArray[2][0].");
    203  test_has_result(results, "propE");
    204 
    205  info("Test that suggestions are given for nested arrays.");
    206  results = propertyProvider("testArray[1].propC[0].");
    207  test_has_result(results, "indexOf");
    208 
    209  info("Test that suggestions are given for literal arrays.");
    210  results = propertyProvider("[1,2,3].");
    211  test_has_result(results, "indexOf");
    212 
    213  results = propertyProvider("[1,2,3].h");
    214  test_has_result(results, "hello");
    215 
    216  results = propertyProvider("[1,2,3].hello.w");
    217  test_has_result(results, "world");
    218 
    219  info("Test that suggestions are given for literal arrays with newlines.");
    220  results = propertyProvider("[1,2,3,\n4\n].");
    221  test_has_result(results, "indexOf");
    222 
    223  info("Test that suggestions are given for literal strings.");
    224  results = propertyProvider("'foo'.");
    225  test_has_result(results, "charAt");
    226  results = propertyProvider('"foo".');
    227  test_has_result(results, "charAt");
    228  results = propertyProvider("`foo`.");
    229  test_has_result(results, "charAt");
    230  results = propertyProvider("`foo doc`.");
    231  test_has_result(results, "charAt");
    232  results = propertyProvider('`foo " doc`.');
    233  test_has_result(results, "charAt");
    234  results = propertyProvider("`foo ' doc`.");
    235  test_has_result(results, "charAt");
    236  results = propertyProvider("'[1,2,3]'.");
    237  test_has_result(results, "charAt");
    238  results = propertyProvider("'foo'.h");
    239  test_has_result(results, "hello");
    240  results = propertyProvider("'foo'.hello.w");
    241  test_has_result(results, "world");
    242  results = propertyProvider(`"\\n".`);
    243  test_has_result(results, "charAt");
    244  results = propertyProvider(`'\\r'.`);
    245  test_has_result(results, "charAt");
    246  results = propertyProvider("`\\\\`.");
    247  test_has_result(results, "charAt");
    248  results = propertyProvider(`"🧑".c`);
    249  test_has_result(results, "codePointAt");
    250 
    251  info("Test that suggestions are not given for syntax errors.");
    252  results = propertyProvider("'foo\"");
    253  Assert.equal(null, results);
    254  results = propertyProvider("'foo d");
    255  Assert.equal(null, results);
    256  results = propertyProvider(`"foo d`);
    257  Assert.equal(null, results);
    258  results = propertyProvider("`foo d");
    259  Assert.equal(null, results);
    260  results = propertyProvider("[1,',2]");
    261  Assert.equal(null, results);
    262  results = propertyProvider("'[1,2].");
    263  Assert.equal(null, results);
    264  results = propertyProvider("'foo'..");
    265  Assert.equal(null, results);
    266 
    267  info("Test that suggestions are not given without a dot.");
    268  results = propertyProvider("'foo'");
    269  test_has_no_results(results);
    270  results = propertyProvider("`foo`");
    271  test_has_no_results(results);
    272  results = propertyProvider("[1,2,3]");
    273  test_has_no_results(results);
    274  results = propertyProvider("[1,2,3].\n'foo'");
    275  test_has_no_results(results);
    276 
    277  info("Test that suggestions are not given for index that's out of bounds.");
    278  results = propertyProvider("testArray[10].");
    279  Assert.equal(null, results);
    280 
    281  info("Test that invalid element access syntax does not return anything");
    282  results = propertyProvider("testArray[][1].");
    283  Assert.equal(null, results);
    284 
    285  info("Test that deep element access works.");
    286  results = propertyProvider("testObject['propA'][0].");
    287  test_has_result(results, "propB");
    288 
    289  results = propertyProvider("testArray[1]['propC'].");
    290  test_has_result(results, "shift");
    291 
    292  results = propertyProvider("testArray[1].propC[0][");
    293  test_has_result(results, `"trim"`);
    294 
    295  results = propertyProvider("testArray[1].propC[0].");
    296  test_has_result(results, "trim");
    297 
    298  info(
    299    "Test that suggestions are displayed when variable is wrapped in parens"
    300  );
    301  results = propertyProvider("(testObject)['propA'][0].");
    302  test_has_result(results, "propB");
    303 
    304  results = propertyProvider("(testArray)[1]['propC'].");
    305  test_has_result(results, "shift");
    306 
    307  results = propertyProvider("(testArray)[1].propC[0][");
    308  test_has_result(results, `"trim"`);
    309 
    310  results = propertyProvider("(testArray)[1].propC[0].");
    311  test_has_result(results, "trim");
    312 
    313  info("Test that suggestions are given if there is an hyphen in the chain.");
    314  results = propertyProvider("testHyphenated['prop-A'].");
    315  test_has_result(results, "trim");
    316 
    317  info("Test that we have suggestions for generators.");
    318  const gen1Result = Cu.evalInSandbox("gen1.next().value", sandbox);
    319  results = propertyProvider("gen1.");
    320  test_has_result(results, "next");
    321  info("Test that the generator next() was not executed");
    322  const gen1NextResult = Cu.evalInSandbox("gen1.next().value", sandbox);
    323  Assert.equal(gen1Result + 1, gen1NextResult);
    324 
    325  info("Test with an anonymous generator.");
    326  const gen2Result = Cu.evalInSandbox("gen2.next().value", sandbox);
    327  results = propertyProvider("gen2.");
    328  test_has_result(results, "next");
    329  const gen2NextResult = Cu.evalInSandbox("gen2.next().value", sandbox);
    330  Assert.equal(gen2Result + 1, gen2NextResult);
    331 
    332  info(
    333    "Test that getters are not executed if authorizedEvaluations is undefined"
    334  );
    335  results = propertyProvider("testGetters.x.");
    336  Assert.deepEqual(results, {
    337    isUnsafeGetter: true,
    338    getterPath: ["testGetters", "x"],
    339  });
    340 
    341  results = propertyProvider("testGetters.x[");
    342  Assert.deepEqual(results, {
    343    isUnsafeGetter: true,
    344    getterPath: ["testGetters", "x"],
    345  });
    346 
    347  results = propertyProvider("testGetters.x.hell");
    348  Assert.deepEqual(results, {
    349    isUnsafeGetter: true,
    350    getterPath: ["testGetters", "x"],
    351  });
    352 
    353  results = propertyProvider("testGetters.x['hell");
    354  Assert.deepEqual(results, {
    355    isUnsafeGetter: true,
    356    getterPath: ["testGetters", "x"],
    357  });
    358 
    359  info(
    360    "Test that getters are not executed if authorizedEvaluations does not match"
    361  );
    362  results = propertyProvider("testGetters.x.", { authorizedEvaluations: [] });
    363  Assert.deepEqual(results, {
    364    isUnsafeGetter: true,
    365    getterPath: ["testGetters", "x"],
    366  });
    367 
    368  results = propertyProvider("testGetters.x.", {
    369    authorizedEvaluations: [["testGetters"]],
    370  });
    371  Assert.deepEqual(results, {
    372    isUnsafeGetter: true,
    373    getterPath: ["testGetters", "x"],
    374  });
    375 
    376  results = propertyProvider("testGetters.x.", {
    377    authorizedEvaluations: [["testGtrs", "x"]],
    378  });
    379  Assert.deepEqual(results, {
    380    isUnsafeGetter: true,
    381    getterPath: ["testGetters", "x"],
    382  });
    383 
    384  results = propertyProvider("testGetters.x.", {
    385    authorizedEvaluations: [["x"]],
    386  });
    387  Assert.deepEqual(results, {
    388    isUnsafeGetter: true,
    389    getterPath: ["testGetters", "x"],
    390  });
    391 
    392  info("Test that deep getter property access returns intermediate getters");
    393  results = propertyProvider("testGetters.y.y.");
    394  Assert.deepEqual(results, {
    395    isUnsafeGetter: true,
    396    getterPath: ["testGetters", "y"],
    397  });
    398 
    399  results = propertyProvider("testGetters['y'].y.");
    400  Assert.deepEqual(results, {
    401    isUnsafeGetter: true,
    402    getterPath: ["testGetters", "y"],
    403  });
    404 
    405  results = propertyProvider("testGetters['y']['y'].");
    406  Assert.deepEqual(results, {
    407    isUnsafeGetter: true,
    408    getterPath: ["testGetters", "y"],
    409  });
    410 
    411  results = propertyProvider("testGetters.y['y'].");
    412  Assert.deepEqual(results, {
    413    isUnsafeGetter: true,
    414    getterPath: ["testGetters", "y"],
    415  });
    416 
    417  info("Test that deep getter property access invoke intermediate getters");
    418  results = propertyProvider("testGetters.y.y.", {
    419    authorizedEvaluations: [["testGetters", "y"]],
    420  });
    421  Assert.deepEqual(results, {
    422    isUnsafeGetter: true,
    423    getterPath: ["testGetters", "y", "y"],
    424  });
    425 
    426  results = propertyProvider("testGetters['y'].y.", {
    427    authorizedEvaluations: [["testGetters", "y"]],
    428  });
    429  Assert.deepEqual(results, {
    430    isUnsafeGetter: true,
    431    getterPath: ["testGetters", "y", "y"],
    432  });
    433 
    434  results = propertyProvider("testGetters['y']['y'].", {
    435    authorizedEvaluations: [["testGetters", "y"]],
    436  });
    437  Assert.deepEqual(results, {
    438    isUnsafeGetter: true,
    439    getterPath: ["testGetters", "y", "y"],
    440  });
    441 
    442  results = propertyProvider("testGetters.y['y'].", {
    443    authorizedEvaluations: [["testGetters", "y"]],
    444  });
    445  Assert.deepEqual(results, {
    446    isUnsafeGetter: true,
    447    getterPath: ["testGetters", "y", "y"],
    448  });
    449 
    450  info(
    451    "Test that getters are executed if matching an authorizedEvaluation element"
    452  );
    453  results = propertyProvider("testGetters.x.", {
    454    authorizedEvaluations: [["testGetters", "x"]],
    455  });
    456  test_has_exact_results(results, ["hello", "world"]);
    457  Assert.strictEqual(Object.keys(results).includes("isUnsafeGetter"), false);
    458  Assert.strictEqual(Object.keys(results).includes("getterPath"), false);
    459 
    460  results = propertyProvider("testGetters.x.", {
    461    authorizedEvaluations: [["testGetters", "x"], ["y"]],
    462  });
    463  test_has_exact_results(results, ["hello", "world"]);
    464  Assert.strictEqual(Object.keys(results).includes("isUnsafeGetter"), false);
    465  Assert.strictEqual(Object.keys(results).includes("getterPath"), false);
    466 
    467  info("Test that executing getters filters with provided string");
    468  results = propertyProvider("testGetters.x.hell", {
    469    authorizedEvaluations: [["testGetters", "x"]],
    470  });
    471  test_has_exact_results(results, ["hello"]);
    472 
    473  results = propertyProvider("testGetters.x['hell", {
    474    authorizedEvaluations: [["testGetters", "x"]],
    475  });
    476  test_has_exact_results(results, ["'hello'"]);
    477 
    478  info(
    479    "Test children getters are not executed if not included in authorizedEvaluation"
    480  );
    481  results = propertyProvider("testGetters.y.y.", {
    482    authorizedEvaluations: [["testGetters", "y", "y"]],
    483  });
    484  Assert.deepEqual(results, {
    485    isUnsafeGetter: true,
    486    getterPath: ["testGetters", "y"],
    487  });
    488 
    489  info(
    490    "Test children getters are executed if matching an authorizedEvaluation element"
    491  );
    492  results = propertyProvider("testGetters.y.y.", {
    493    authorizedEvaluations: [
    494      ["testGetters", "y"],
    495      ["testGetters", "y", "y"],
    496    ],
    497  });
    498  test_has_result(results, "trim");
    499 
    500  info("Test with number literals");
    501  results = propertyProvider("1.");
    502  Assert.strictEqual(
    503    results,
    504    null,
    505    "Does not complete on possible floating number"
    506  );
    507 
    508  results = propertyProvider("(1)..");
    509  Assert.strictEqual(results, null, "Does not complete on invalid syntax");
    510 
    511  results = propertyProvider("(1.1.).");
    512  Assert.strictEqual(results, null, "Does not complete on invalid syntax");
    513 
    514  results = propertyProvider("1..");
    515  test_has_result(results, "toFixed");
    516 
    517  results = propertyProvider("1 .");
    518  test_has_result(results, "toFixed");
    519 
    520  results = propertyProvider("1\n.");
    521  test_has_result(results, "toFixed");
    522 
    523  results = propertyProvider(".1.");
    524  test_has_result(results, "toFixed");
    525 
    526  results = propertyProvider("1[");
    527  test_has_result(results, `"toFixed"`);
    528 
    529  results = propertyProvider("1[toFixed");
    530  test_has_exact_results(results, [`"toFixed"`]);
    531 
    532  results = propertyProvider("1['toFixed");
    533  test_has_exact_results(results, ["'toFixed'"]);
    534 
    535  results = propertyProvider("1.1[");
    536  test_has_result(results, `"toFixed"`);
    537 
    538  results = propertyProvider("(1).");
    539  test_has_result(results, "toFixed");
    540 
    541  results = propertyProvider("(.1).");
    542  test_has_result(results, "toFixed");
    543 
    544  results = propertyProvider("(1.1).");
    545  test_has_result(results, "toFixed");
    546 
    547  results = propertyProvider("(1).toFixed");
    548  test_has_exact_results(results, ["toFixed"]);
    549 
    550  results = propertyProvider("(1)[");
    551  test_has_result(results, `"toFixed"`);
    552 
    553  results = propertyProvider("(1.1)[");
    554  test_has_result(results, `"toFixed"`);
    555 
    556  results = propertyProvider("(1)[toFixed");
    557  test_has_exact_results(results, [`"toFixed"`]);
    558 
    559  results = propertyProvider("(1)['toFixed");
    560  test_has_exact_results(results, ["'toFixed'"]);
    561 
    562  results = propertyProvider("(1).h");
    563  test_has_result(results, "hello");
    564 
    565  results = propertyProvider("(1).hello.w");
    566  test_has_result(results, "world");
    567 
    568  info("Test access on dot-notation invalid property name");
    569  results = propertyProvider("testHyphenated.prop");
    570  Assert.ok(
    571    !results.matches.has("prop-A"),
    572    "Does not return invalid property name on dot access"
    573  );
    574 
    575  results = propertyProvider("testHyphenated['prop");
    576  test_has_result(results, `'prop-A'`);
    577 
    578  results = propertyProvider(`//t`);
    579  Assert.strictEqual(results, null, "Does not complete in inline comment");
    580 
    581  results = propertyProvider(`// t`);
    582  Assert.strictEqual(
    583    results,
    584    null,
    585    "Does not complete in inline comment after space"
    586  );
    587 
    588  results = propertyProvider(`//I'm a comment\nt`);
    589  test_has_result(results, "testObject");
    590 
    591  results = propertyProvider(`1/t`);
    592  test_has_result(results, "testObject");
    593 
    594  results = propertyProvider(`1/(t`);
    595  test_has_result(results, "testObject");
    596 
    597  results = propertyProvider(`/* t`);
    598  Assert.strictEqual(results, null, "Does not complete in multiline comment");
    599 
    600  results = propertyProvider(`/*I'm\nt`);
    601  Assert.strictEqual(
    602    results,
    603    null,
    604    "Does not complete in multiline comment after line break"
    605  );
    606 
    607  results = propertyProvider(`/*I'm a comment\n \t * /t`);
    608  Assert.strictEqual(
    609    results,
    610    null,
    611    "Does not complete in multiline comment after line break and invalid comment end"
    612  );
    613 
    614  results = propertyProvider(`/*I'm a comment\n \t */t`);
    615  test_has_result(results, "testObject");
    616 
    617  results = propertyProvider(`/*I'm a comment\n \t */\n\nt`);
    618  test_has_result(results, "testObject");
    619 
    620  info("Test local expression variables");
    621  results = propertyProvider("b", { expressionVars: ["a", "b", "c"] });
    622  test_has_result(results, "b");
    623  Assert.equal(results.matches.has("a"), false);
    624  Assert.equal(results.matches.has("c"), false);
    625 
    626  info(
    627    "Test that local expression variables are not included when accessing an object properties"
    628  );
    629  results = propertyProvider("testObject.prop", {
    630    expressionVars: ["propLocal"],
    631  });
    632  Assert.equal(results.matches.has("propLocal"), false);
    633  test_has_result(results, "propA");
    634 
    635  results = propertyProvider("testObject['prop", {
    636    expressionVars: ["propLocal"],
    637  });
    638  test_has_result(results, "'propA'");
    639  Assert.equal(results.matches.has("propLocal"), false);
    640 
    641  info("Test that expression with optional chaining operator are completed");
    642  results = propertyProvider("testObject?.prop");
    643  test_has_result(results, "propA");
    644 
    645  results = propertyProvider("testObject?.propA[0]?.propB?.to");
    646  test_has_result(results, "toString");
    647 
    648  results = propertyProvider("testObject?.propA?.[0]?.propB?.to");
    649  test_has_result(results, "toString");
    650 
    651  results = propertyProvider(
    652    "testObject      ?.    propA[0]    ?.    propB  ?.   to"
    653  );
    654  test_has_result(results, "toString");
    655 
    656  results = propertyProvider("testObject?.[prop");
    657  test_has_result(results, '"propA"');
    658 
    659  results = propertyProvider(`testObject?.["prop`);
    660  test_has_result(results, '"propA"');
    661 
    662  results = propertyProvider(`testObject?.['prop`);
    663  test_has_result(results, `'propA'`);
    664 
    665  results = propertyProvider(`testObject?.["propA"]?.[0]?.["propB"]?.["to`);
    666  test_has_result(results, `"toString"`);
    667 
    668  results = propertyProvider(
    669    `testObject  ?.    ["propA"]  ?.   [0]  ?.   ["propB"]  ?.  ['to`
    670  );
    671  test_has_result(results, "'toString'");
    672 
    673  results = propertyProvider("[1,2,3]?.");
    674  test_has_result(results, "indexOf");
    675 
    676  results = propertyProvider("'foo'?.");
    677  test_has_result(results, "charAt");
    678 
    679  results = propertyProvider("1?.");
    680  test_has_result(results, "toFixed");
    681 
    682  // check this doesn't throw since `propC` is not defined.
    683  results = propertyProvider("testObject?.propC?.this?.does?.not?.exist?.d");
    684 
    685  // check that ternary operator isn't mistaken for optional chaining
    686  results = propertyProvider(`true?.3.to`);
    687  test_has_result(results, `toExponential`);
    688 
    689  results = propertyProvider(`true?.3?.to`);
    690  test_has_result(results, `toExponential`);
    691 
    692  // Test more ternary
    693  results = propertyProvider(`true?t`);
    694  test_has_result(results, `testObject`);
    695 
    696  results = propertyProvider(`true??t`);
    697  test_has_result(results, `testObject`);
    698 
    699  results = propertyProvider(`true?/* comment */t`);
    700  test_has_result(results, `testObject`);
    701 
    702  results = propertyProvider(`true?<t`);
    703  test_has_no_results(results);
    704 
    705  // Test autocompletion on debugger statement does not throw
    706  results = propertyProvider(`debugger.`);
    707  Assert.strictEqual(results, null, "Does not complete a debugger keyword");
    708 
    709  // Test autocompletion on Proxies
    710  // proxy does not get autocompletion result from prototype defined in `getPrototypeOf`
    711  test_has_no_results(propertyProvider(`testArrayPrototypeProxy.filte`));
    712  results = propertyProvider(`testArrayPrototypeProxy.`);
    713  // it does get the own property
    714  test_has_result(results, `world`);
    715  // as well as method from the actual proxy target prototype
    716  test_has_result(results, `hasOwnProperty`);
    717 
    718  results = propertyProvider(`testSelfPrototypeProxy.`);
    719  test_has_result(results, `hello`);
    720  test_has_result(results, `hasOwnProperty`);
    721 
    722  info("Test suggestion for Infinity");
    723  results = propertyProvider("Inf");
    724  test_has_result(results, "Infinity");
    725 }
    726 
    727 /**
    728 * A helper that ensures an empty array of results were found.
    729 *
    730 * @param Object results
    731 *        The results returned by jsPropertyProvider.
    732 */
    733 function test_has_no_results(results) {
    734  Assert.notEqual(results, null);
    735  Assert.equal(results.matches.size, 0);
    736 }
    737 /**
    738 * A helper that ensures (required) results were found.
    739 *
    740 * @param Object results
    741 *        The results returned by jsPropertyProvider.
    742 * @param String requiredSuggestion
    743 *        A suggestion that must be found from the results.
    744 */
    745 function test_has_result(results, requiredSuggestion) {
    746  Assert.notEqual(results, null);
    747  Assert.greater(results.matches.size, 0);
    748  Assert.ok(
    749    results.matches.has(requiredSuggestion),
    750    `<${requiredSuggestion}> found in ${[...results.matches.values()].join(
    751      " - "
    752    )}`
    753  );
    754 }
    755 
    756 /**
    757 * A helper that ensures results are the expected ones.
    758 *
    759 * @param Object results
    760 *        The results returned by jsPropertyProvider.
    761 * @param Array expectedMatches
    762 *        An array of the properties that should be returned by jsPropertyProvider.
    763 */
    764 function test_has_exact_results(results, expectedMatches) {
    765  Assert.deepEqual([...results.matches], expectedMatches);
    766 }