Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

// Any copyright is dedicated to the Public Domain.
"use strict";
const {
fallibleJsPropertyProvider: jsPropertyProvider,
const { addDebuggerToGlobal } = ChromeUtils.importESModule(
);
addDebuggerToGlobal(globalThis);
function run_test() {
Services.prefs.setBoolPref(
"security.allow_parent_unrestricted_js_loads",
true
);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
});
const testArray = `var testArray = [
{propA: "A"},
{
propB: "B",
propC: [
"D"
]
},
[
{propE: "E"}
]
]`;
const testObject = 'var testObject = {"propA": [{"propB": "B"}]}';
const testHyphenated = 'var testHyphenated = {"prop-A": "res-A"}';
const testLet = "let foobar = {a: ''}; const blargh = {a: 1};";
const testGenerators = `
// Test with generator using a named function.
function* genFunc() {
for (let i = 0; i < 10; i++) {
yield i;
}
}
let gen1 = genFunc();
gen1.next();
// Test with generator using an anonymous function.
let gen2 = (function* () {
for (let i = 0; i < 10; i++) {
yield i;
}
})();`;
const testGetters = `
var testGetters = {
get x() {
return Object.create(null, Object.getOwnPropertyDescriptors({
hello: "",
world: "",
}));
},
get y() {
return Object.create(null, Object.getOwnPropertyDescriptors({
get y() {
return "plop";
},
}));
}
};
`;
const testProxies = `
var testSelfPrototypeProxy = new Proxy({
hello: 1
}, {
getPrototypeOf: () => testProxy
});
var testArrayPrototypeProxy = new Proxy({
world: 2
}, {
getPrototypeOf: () => Array.prototype
})
`;
const sandbox = Cu.Sandbox("http://example.com");
const dbg = new Debugger();
const dbgObject = dbg.addDebuggee(sandbox);
const dbgEnv = dbgObject.asEnvironment();
Cu.evalInSandbox(
`
const hello = Object.create(null, Object.getOwnPropertyDescriptors({world: 1}));
String.prototype.hello = hello;
Number.prototype.hello = hello;
Array.prototype.hello = hello;
`,
sandbox
);
Cu.evalInSandbox(testArray, sandbox);
Cu.evalInSandbox(testObject, sandbox);
Cu.evalInSandbox(testHyphenated, sandbox);
Cu.evalInSandbox(testLet, sandbox);
Cu.evalInSandbox(testGenerators, sandbox);
Cu.evalInSandbox(testGetters, sandbox);
Cu.evalInSandbox(testProxies, sandbox);
info("Running tests with dbgObject");
runChecks(dbgObject, null, sandbox);
info("Running tests with dbgEnv");
runChecks(null, dbgEnv, sandbox);
}
function runChecks(dbgObject, environment, sandbox) {
const propertyProvider = (inputValue, options) =>
jsPropertyProvider({
dbgObject,
environment,
inputValue,
...options,
});
info("Test that suggestions are given for 'this'");
let results = propertyProvider("t");
test_has_result(results, "this");
if (dbgObject != null) {
info("Test that suggestions are given for 'this.'");
results = propertyProvider("this.");
test_has_result(results, "testObject");
info("Test that suggestions are given for '(this).'");
results = propertyProvider("(this).");
test_has_result(results, "testObject");
info("Test that suggestions are given for deep 'this' properties access");
results = propertyProvider("(this).testObject.propA.");
test_has_result(results, "shift");
results = propertyProvider("(this).testObject.propA[");
test_has_result(results, `"shift"`);
results = propertyProvider("(this)['testObject']['propA'][");
test_has_result(results, `"shift"`);
results = propertyProvider("(this).testObject['propA'].");
test_has_result(results, "shift");
info("Test that no suggestions are given for 'this.this'");
results = propertyProvider("this.this");
test_has_no_results(results);
}
info("Test that suggestions are given for 'globalThis'");
results = propertyProvider("g");
test_has_result(results, "globalThis");
info("Test that suggestions are given for 'globalThis.'");
results = propertyProvider("globalThis.");
test_has_result(results, "testObject");
info("Test that suggestions are given for '(globalThis).'");
results = propertyProvider("(globalThis).");
test_has_result(results, "testObject");
test_has_result(results, "Infinity");
info(
"Test that suggestions are given for deep 'globalThis' properties access"
);
results = propertyProvider("(globalThis).testObject.propA.");
test_has_result(results, "shift");
results = propertyProvider("(globalThis).testObject.propA[");
test_has_result(results, `"shift"`);
results = propertyProvider("(globalThis)['testObject']['propA'][");
test_has_result(results, `"shift"`);
results = propertyProvider("(globalThis).testObject['propA'].");
test_has_result(results, "shift");
info("Testing lexical scope issues (Bug 1207868)");
results = propertyProvider("foobar");
test_has_result(results, "foobar");
results = propertyProvider("foobar.");
test_has_result(results, "a");
results = propertyProvider("blargh");
test_has_result(results, "blargh");
results = propertyProvider("blargh.");
test_has_result(results, "a");
info("Test that suggestions are given for 'foo[n]' where n is an integer.");
results = propertyProvider("testArray[0].");
test_has_result(results, "propA");
info("Test that suggestions are given for multidimensional arrays.");
results = propertyProvider("testArray[2][0].");
test_has_result(results, "propE");
info("Test that suggestions are given for nested arrays.");
results = propertyProvider("testArray[1].propC[0].");
test_has_result(results, "indexOf");
info("Test that suggestions are given for literal arrays.");
results = propertyProvider("[1,2,3].");
test_has_result(results, "indexOf");
results = propertyProvider("[1,2,3].h");
test_has_result(results, "hello");
results = propertyProvider("[1,2,3].hello.w");
test_has_result(results, "world");
info("Test that suggestions are given for literal arrays with newlines.");
results = propertyProvider("[1,2,3,\n4\n].");
test_has_result(results, "indexOf");
info("Test that suggestions are given for literal strings.");
results = propertyProvider("'foo'.");
test_has_result(results, "charAt");
results = propertyProvider('"foo".');
test_has_result(results, "charAt");
results = propertyProvider("`foo`.");
test_has_result(results, "charAt");
results = propertyProvider("`foo doc`.");
test_has_result(results, "charAt");
results = propertyProvider('`foo " doc`.');
test_has_result(results, "charAt");
results = propertyProvider("`foo ' doc`.");
test_has_result(results, "charAt");
results = propertyProvider("'[1,2,3]'.");
test_has_result(results, "charAt");
results = propertyProvider("'foo'.h");
test_has_result(results, "hello");
results = propertyProvider("'foo'.hello.w");
test_has_result(results, "world");
results = propertyProvider(`"\\n".`);
test_has_result(results, "charAt");
results = propertyProvider(`'\\r'.`);
test_has_result(results, "charAt");
results = propertyProvider("`\\\\`.");
test_has_result(results, "charAt");
info("Test that suggestions are not given for syntax errors.");
results = propertyProvider("'foo\"");
Assert.equal(null, results);
results = propertyProvider("'foo d");
Assert.equal(null, results);
results = propertyProvider(`"foo d`);
Assert.equal(null, results);
results = propertyProvider("`foo d");
Assert.equal(null, results);
results = propertyProvider("[1,',2]");
Assert.equal(null, results);
results = propertyProvider("'[1,2].");
Assert.equal(null, results);
results = propertyProvider("'foo'..");
Assert.equal(null, results);
info("Test that suggestions are not given without a dot.");
results = propertyProvider("'foo'");
test_has_no_results(results);
results = propertyProvider("`foo`");
test_has_no_results(results);
results = propertyProvider("[1,2,3]");
test_has_no_results(results);
results = propertyProvider("[1,2,3].\n'foo'");
test_has_no_results(results);
info("Test that suggestions are not given for index that's out of bounds.");
results = propertyProvider("testArray[10].");
Assert.equal(null, results);
info("Test that invalid element access syntax does not return anything");
results = propertyProvider("testArray[][1].");
Assert.equal(null, results);
info("Test that deep element access works.");
results = propertyProvider("testObject['propA'][0].");
test_has_result(results, "propB");
results = propertyProvider("testArray[1]['propC'].");
test_has_result(results, "shift");
results = propertyProvider("testArray[1].propC[0][");
test_has_result(results, `"trim"`);
results = propertyProvider("testArray[1].propC[0].");
test_has_result(results, "trim");
info(
"Test that suggestions are displayed when variable is wrapped in parens"
);
results = propertyProvider("(testObject)['propA'][0].");
test_has_result(results, "propB");
results = propertyProvider("(testArray)[1]['propC'].");
test_has_result(results, "shift");
results = propertyProvider("(testArray)[1].propC[0][");
test_has_result(results, `"trim"`);
results = propertyProvider("(testArray)[1].propC[0].");
test_has_result(results, "trim");
info("Test that suggestions are given if there is an hyphen in the chain.");
results = propertyProvider("testHyphenated['prop-A'].");
test_has_result(results, "trim");
info("Test that we have suggestions for generators.");
const gen1Result = Cu.evalInSandbox("gen1.next().value", sandbox);
results = propertyProvider("gen1.");
test_has_result(results, "next");
info("Test that the generator next() was not executed");
const gen1NextResult = Cu.evalInSandbox("gen1.next().value", sandbox);
Assert.equal(gen1Result + 1, gen1NextResult);
info("Test with an anonymous generator.");
const gen2Result = Cu.evalInSandbox("gen2.next().value", sandbox);
results = propertyProvider("gen2.");
test_has_result(results, "next");
const gen2NextResult = Cu.evalInSandbox("gen2.next().value", sandbox);
Assert.equal(gen2Result + 1, gen2NextResult);
info(
"Test that getters are not executed if authorizedEvaluations is undefined"
);
results = propertyProvider("testGetters.x.");
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "x"],
});
results = propertyProvider("testGetters.x[");
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "x"],
});
results = propertyProvider("testGetters.x.hell");
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "x"],
});
results = propertyProvider("testGetters.x['hell");
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "x"],
});
info(
"Test that getters are not executed if authorizedEvaluations does not match"
);
results = propertyProvider("testGetters.x.", { authorizedEvaluations: [] });
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "x"],
});
results = propertyProvider("testGetters.x.", {
authorizedEvaluations: [["testGetters"]],
});
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "x"],
});
results = propertyProvider("testGetters.x.", {
authorizedEvaluations: [["testGtrs", "x"]],
});
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "x"],
});
results = propertyProvider("testGetters.x.", {
authorizedEvaluations: [["x"]],
});
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "x"],
});
info("Test that deep getter property access returns intermediate getters");
results = propertyProvider("testGetters.y.y.");
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "y"],
});
results = propertyProvider("testGetters['y'].y.");
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "y"],
});
results = propertyProvider("testGetters['y']['y'].");
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "y"],
});
results = propertyProvider("testGetters.y['y'].");
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "y"],
});
info("Test that deep getter property access invoke intermediate getters");
results = propertyProvider("testGetters.y.y.", {
authorizedEvaluations: [["testGetters", "y"]],
});
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "y", "y"],
});
results = propertyProvider("testGetters['y'].y.", {
authorizedEvaluations: [["testGetters", "y"]],
});
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "y", "y"],
});
results = propertyProvider("testGetters['y']['y'].", {
authorizedEvaluations: [["testGetters", "y"]],
});
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "y", "y"],
});
results = propertyProvider("testGetters.y['y'].", {
authorizedEvaluations: [["testGetters", "y"]],
});
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "y", "y"],
});
info(
"Test that getters are executed if matching an authorizedEvaluation element"
);
results = propertyProvider("testGetters.x.", {
authorizedEvaluations: [["testGetters", "x"]],
});
test_has_exact_results(results, ["hello", "world"]);
Assert.ok(Object.keys(results).includes("isUnsafeGetter") === false);
Assert.ok(Object.keys(results).includes("getterPath") === false);
results = propertyProvider("testGetters.x.", {
authorizedEvaluations: [["testGetters", "x"], ["y"]],
});
test_has_exact_results(results, ["hello", "world"]);
Assert.ok(Object.keys(results).includes("isUnsafeGetter") === false);
Assert.ok(Object.keys(results).includes("getterPath") === false);
info("Test that executing getters filters with provided string");
results = propertyProvider("testGetters.x.hell", {
authorizedEvaluations: [["testGetters", "x"]],
});
test_has_exact_results(results, ["hello"]);
results = propertyProvider("testGetters.x['hell", {
authorizedEvaluations: [["testGetters", "x"]],
});
test_has_exact_results(results, ["'hello'"]);
info(
"Test children getters are not executed if not included in authorizedEvaluation"
);
results = propertyProvider("testGetters.y.y.", {
authorizedEvaluations: [["testGetters", "y", "y"]],
});
Assert.deepEqual(results, {
isUnsafeGetter: true,
getterPath: ["testGetters", "y"],
});
info(
"Test children getters are executed if matching an authorizedEvaluation element"
);
results = propertyProvider("testGetters.y.y.", {
authorizedEvaluations: [
["testGetters", "y"],
["testGetters", "y", "y"],
],
});
test_has_result(results, "trim");
info("Test with number literals");
results = propertyProvider("1.");
Assert.ok(results === null, "Does not complete on possible floating number");
results = propertyProvider("(1)..");
Assert.ok(results === null, "Does not complete on invalid syntax");
results = propertyProvider("(1.1.).");
Assert.ok(results === null, "Does not complete on invalid syntax");
results = propertyProvider("1..");
test_has_result(results, "toFixed");
results = propertyProvider("1 .");
test_has_result(results, "toFixed");
results = propertyProvider("1\n.");
test_has_result(results, "toFixed");
results = propertyProvider(".1.");
test_has_result(results, "toFixed");
results = propertyProvider("1[");
test_has_result(results, `"toFixed"`);
results = propertyProvider("1[toFixed");
test_has_exact_results(results, [`"toFixed"`]);
results = propertyProvider("1['toFixed");
test_has_exact_results(results, ["'toFixed'"]);
results = propertyProvider("1.1[");
test_has_result(results, `"toFixed"`);
results = propertyProvider("(1).");
test_has_result(results, "toFixed");
results = propertyProvider("(.1).");
test_has_result(results, "toFixed");
results = propertyProvider("(1.1).");
test_has_result(results, "toFixed");
results = propertyProvider("(1).toFixed");
test_has_exact_results(results, ["toFixed"]);
results = propertyProvider("(1)[");
test_has_result(results, `"toFixed"`);
results = propertyProvider("(1.1)[");
test_has_result(results, `"toFixed"`);
results = propertyProvider("(1)[toFixed");
test_has_exact_results(results, [`"toFixed"`]);
results = propertyProvider("(1)['toFixed");
test_has_exact_results(results, ["'toFixed'"]);
results = propertyProvider("(1).h");
test_has_result(results, "hello");
results = propertyProvider("(1).hello.w");
test_has_result(results, "world");
info("Test access on dot-notation invalid property name");
results = propertyProvider("testHyphenated.prop");
Assert.ok(
!results.matches.has("prop-A"),
"Does not return invalid property name on dot access"
);
results = propertyProvider("testHyphenated['prop");
test_has_result(results, `'prop-A'`);
results = propertyProvider(`//t`);
Assert.ok(results === null, "Does not complete in inline comment");
results = propertyProvider(`// t`);
Assert.ok(
results === null,
"Does not complete in inline comment after space"
);
results = propertyProvider(`//I'm a comment\nt`);
test_has_result(results, "testObject");
results = propertyProvider(`1/t`);
test_has_result(results, "testObject");
results = propertyProvider(`/* t`);
Assert.ok(results === null, "Does not complete in multiline comment");
results = propertyProvider(`/*I'm\nt`);
Assert.ok(
results === null,
"Does not complete in multiline comment after line break"
);
results = propertyProvider(`/*I'm a comment\n \t * /t`);
Assert.ok(
results === null,
"Does not complete in multiline comment after line break and invalid comment end"
);
results = propertyProvider(`/*I'm a comment\n \t */t`);
test_has_result(results, "testObject");
results = propertyProvider(`/*I'm a comment\n \t */\n\nt`);
test_has_result(results, "testObject");
info("Test local expression variables");
results = propertyProvider("b", { expressionVars: ["a", "b", "c"] });
test_has_result(results, "b");
Assert.equal(results.matches.has("a"), false);
Assert.equal(results.matches.has("c"), false);
info(
"Test that local expression variables are not included when accessing an object properties"
);
results = propertyProvider("testObject.prop", {
expressionVars: ["propLocal"],
});
Assert.equal(results.matches.has("propLocal"), false);
test_has_result(results, "propA");
results = propertyProvider("testObject['prop", {
expressionVars: ["propLocal"],
});
test_has_result(results, "'propA'");
Assert.equal(results.matches.has("propLocal"), false);
info("Test that expression with optional chaining operator are completed");
results = propertyProvider("testObject?.prop");
test_has_result(results, "propA");
results = propertyProvider("testObject?.propA[0]?.propB?.to");
test_has_result(results, "toString");
results = propertyProvider("testObject?.propA?.[0]?.propB?.to");
test_has_result(results, "toString");
results = propertyProvider(
"testObject ?. propA[0] ?. propB ?. to"
);
test_has_result(results, "toString");
results = propertyProvider("testObject?.[prop");
test_has_result(results, '"propA"');
results = propertyProvider(`testObject?.["prop`);
test_has_result(results, '"propA"');
results = propertyProvider(`testObject?.['prop`);
test_has_result(results, `'propA'`);
results = propertyProvider(`testObject?.["propA"]?.[0]?.["propB"]?.["to`);
test_has_result(results, `"toString"`);
results = propertyProvider(
`testObject ?. ["propA"] ?. [0] ?. ["propB"] ?. ['to`
);
test_has_result(results, "'toString'");
results = propertyProvider("[1,2,3]?.");
test_has_result(results, "indexOf");
results = propertyProvider("'foo'?.");
test_has_result(results, "charAt");
results = propertyProvider("1?.");
test_has_result(results, "toFixed");
// check this doesn't throw since `propC` is not defined.
results = propertyProvider("testObject?.propC?.this?.does?.not?.exist?.d");
// check that ternary operator isn't mistaken for optional chaining
results = propertyProvider(`true?.3.to`);
test_has_result(results, `toExponential`);
results = propertyProvider(`true?.3?.to`);
test_has_result(results, `toExponential`);
// Test more ternary
results = propertyProvider(`true?t`);
test_has_result(results, `testObject`);
results = propertyProvider(`true??t`);
test_has_result(results, `testObject`);
results = propertyProvider(`true?/* comment */t`);
test_has_result(results, `testObject`);
results = propertyProvider(`true?<t`);
test_has_no_results(results);
// Test autocompletion on debugger statement does not throw
results = propertyProvider(`debugger.`);
Assert.ok(results === null, "Does not complete a debugger keyword");
// Test autocompletion on Proxies
// proxy does not get autocompletion result from prototype defined in `getPrototypeOf`
test_has_no_results(propertyProvider(`testArrayPrototypeProxy.filte`));
results = propertyProvider(`testArrayPrototypeProxy.`);
// it does get the own property
test_has_result(results, `world`);
// as well as method from the actual proxy target prototype
test_has_result(results, `hasOwnProperty`);
results = propertyProvider(`testSelfPrototypeProxy.`);
test_has_result(results, `hello`);
test_has_result(results, `hasOwnProperty`);
info("Test suggestion for Infinity");
results = propertyProvider("Inf");
test_has_result(results, "Infinity");
}
/**
* A helper that ensures an empty array of results were found.
* @param Object results
* The results returned by jsPropertyProvider.
*/
function test_has_no_results(results) {
Assert.notEqual(results, null);
Assert.equal(results.matches.size, 0);
}
/**
* A helper that ensures (required) results were found.
* @param Object results
* The results returned by jsPropertyProvider.
* @param String requiredSuggestion
* A suggestion that must be found from the results.
*/
function test_has_result(results, requiredSuggestion) {
Assert.notEqual(results, null);
Assert.ok(results.matches.size > 0);
Assert.ok(
results.matches.has(requiredSuggestion),
`<${requiredSuggestion}> found in ${[...results.matches.values()].join(
" - "
)}`
);
}
/**
* A helper that ensures results are the expected ones.
* @param Object results
* The results returned by jsPropertyProvider.
* @param Array expectedMatches
* An array of the properties that should be returned by jsPropertyProvider.
*/
function test_has_exact_results(results, expectedMatches) {
Assert.deepEqual([...results.matches], expectedMatches);
}