Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf8">
<title>Test for JavaScript terminal functionality</title>
<script type="text/javascript" src="common.js"></script>
<!-- Any copyright is dedicated to the Public Domain.
</head>
<body>
<p>Test for JavaScript terminal autocomplete functionality</p>
<script class="testbody" type="text/javascript">
"use strict";
SimpleTest.waitForExplicitFinish();
const {
MAX_AUTOCOMPLETE_ATTEMPTS,
MAX_AUTOCOMPLETIONS
} = require("devtools/shared/webconsole/js-property-provider");
const RESERVED_JS_KEYWORDS = require("devtools/shared/webconsole/reserved-js-words");
addEventListener("load", startTest);
async function startTest() {
// First run the tests with a tab as a target.
let {state} = await attachConsoleToTab(["PageError"]);
await performTests({state, isWorker: false});
// Then run the tests with a worker as a target.
state = (await attachConsoleToWorker(["PageError"])).state;
await performTests({state, isWorker: true});
SimpleTest.finish();
}
async function performTests({state, isWorker}) {
// Set up the global variables needed to test autocompletion in the target.
const script = `
// This is for workers so autocomplete acts the same
if (!this.window) {
window = this;
}
window.foobarObject = Object.create(null);
window.foobarObject.foo = 1;
window.foobarObject.foobar = 2;
window.foobarObject.foobaz = 3;
window.foobarObject.omg = 4;
window.foobarObject.omgfoo = 5;
window.foobarObject.strfoo = "foobarz";
window.foobarObject.omgstr = "foobarz" +
(new Array(${DevToolsServer.LONG_STRING_LENGTH})).join("abb");
window.largeObject1 = Object.create(null);
for (let i = 0; i < ${MAX_AUTOCOMPLETE_ATTEMPTS + 1}; i++) {
window.largeObject1['a' + i] = i;
}
window.largeObject2 = Object.create(null);
for (let i = 0; i < ${MAX_AUTOCOMPLETIONS * 2}; i++) {
window.largeObject2['a' + i] = i;
}
window.proxy1 = new Proxy({foo: 1}, {
getPrototypeOf() { throw new Error() }
});
window.proxy2 = new Proxy(Object.create(Object.create(null, {foo:{}})), {
ownKeys() { throw new Error() }
});
window.emojiObject = Object.create(null);
window.emojiObject["😎"] = "😎";
window.insensitiveTestCase = Object.create(null, Object.getOwnPropertyDescriptors({
PROP: "",
Prop: "",
prop: "",
PRÖP: "",
pröp: "",
}));
window.elementAccessTestCase = Object.create(null, Object.getOwnPropertyDescriptors({
bar: "",
BAR: "",
dataTest: "",
"data-test": "",
'da"ta"test': "",
'da\`ta\`test': "",
"da'ta'test": "",
}));
window.varify = true;
var Cu_Sandbox = Cu ? Cu.Sandbox : null;
`;
await evaluateExpression(state.webConsoleFront, script);
const tests = [
doAutocomplete1,
doAutocomplete2,
doAutocomplete3,
doAutocomplete4,
doAutocompleteLarge1,
doAutocompleteLarge2,
doAutocompleteProxyThrowsPrototype,
doAutocompleteProxyThrowsOwnKeys,
doAutocompleteDotSurroundedBySpaces,
doAutocompleteAfterOr,
doInsensitiveAutocomplete,
doElementAccessAutocomplete,
doAutocompleteAfterOperator,
dontAutocompleteAfterDeclaration,
doKeywordsAutocomplete,
dontAutocomplete,
];
if (!isWorker) {
// `Cu` is not defined in workers, then we can't test `Cu.Sandbox`
tests.push(doAutocompleteSandbox);
// Some cases are handled in worker context because we can't use parser.js.
// See Bug 1507181.
tests.push(
doAutocompleteArray,
doAutocompleteString,
doAutocompleteCommands,
doAutocompleteBracketSurroundedBySpaces,
);
}
for (const test of tests) {
await test(state.webConsoleFront);
}
// Null out proxy1 and proxy2: the proxy handlers use scripted functions
// that can keep the debugger sandbox alive longer than necessary via their
// environment chain (due to the webconsole helper functions defined there).
await evaluateExpression(state.webConsoleFront, `this.proxy1 = null; this.proxy2 = null;`);
await closeDebugger(state);
}
async function doAutocomplete1(webConsoleFront) {
info("test autocomplete for 'window.foo'");
const response = await webConsoleFront.autocomplete("window.foo");
const matches = response.matches;
is(response.matchProp, "foo", "matchProp");
is(matches.length, 1, "matches.length");
is(matches[0], "foobarObject", "matches[0]");
}
async function doAutocomplete2(webConsoleFront) {
info("test autocomplete for 'window.foobarObject.'");
const response = await webConsoleFront.autocomplete("window.foobarObject.");
const matches = response.matches;
ok(!response.matchProp, "matchProp");
is(matches.length, 7, "matches.length");
checkObject(matches,
["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
}
async function doAutocomplete3(webConsoleFront) {
// Check that completion suggestions are offered inside the string.
info("test autocomplete for 'dump(window.foobarObject.)'");
const response = await webConsoleFront.autocomplete("dump(window.foobarObject.)", 25);
const matches = response.matches;
ok(!response.matchProp, "matchProp");
is(matches.length, 7, "matches.length");
checkObject(matches,
["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
}
async function doAutocomplete4(webConsoleFront) {
// Check that completion requests can have no suggestions.
info("test autocomplete for 'dump(window.foobarObject.)'");
const response = await webConsoleFront.autocomplete("dump(window.foobarObject.)");
ok(!response.matchProp, "matchProp");
is(response.matches, null, "matches is null");
}
async function doAutocompleteLarge1(webConsoleFront) {
// Check that completion requests with too large objects will
// have no suggestions.
info("test autocomplete for 'window.largeObject1.'");
const response = await webConsoleFront.autocomplete("window.largeObject1.");
ok(!response.matchProp, "matchProp");
info (response.matches.join("|"));
is(response.matches.length, 0, "Bailed out with too many properties");
}
async function doAutocompleteLarge2(webConsoleFront) {
// Check that completion requests with pretty large objects will
// have MAX_AUTOCOMPLETIONS suggestions
info("test autocomplete for 'window.largeObject2.'");
const response = await webConsoleFront.autocomplete("window.largeObject2.");
ok(!response.matchProp, "matchProp");
is(response.matches.length, MAX_AUTOCOMPLETIONS, "matches.length is MAX_AUTOCOMPLETIONS");
}
async function doAutocompleteProxyThrowsPrototype(webConsoleFront) {
// Check that completion provides own properties even if [[GetPrototypeOf]] throws.
info("test autocomplete for 'window.proxy1.'");
const response = await webConsoleFront.autocomplete("window.proxy1.");
ok(!response.matchProp, "matchProp");
is(response.matches.length, 14, "matches.length");
ok(response.matches.includes("foo"), "matches has own property for proxy with throwing getPrototypeOf trap");
}
async function doAutocompleteProxyThrowsOwnKeys(webConsoleFront) {
// Check that completion provides inherited properties even if [[OwnPropertyKeys]] throws.
info("test autocomplete for 'window.proxy2.'");
const response = await webConsoleFront.autocomplete("window.proxy2.");
ok(!response.matchProp, "matchProp");
is(response.matches.length, 1, "matches.length");
checkObject(response.matches, ["foo"]);
}
async function doAutocompleteSandbox(webConsoleFront) {
// Check that completion provides inherited properties even if [[OwnPropertyKeys]] throws.
info("test autocomplete for 'Cu_Sandbox.'");
const response = await webConsoleFront.autocomplete("Cu_Sandbox.");
ok(!response.matchProp, "matchProp");
const keys = Object.getOwnPropertyNames(Object.prototype).sort();
is(response.matches.length, keys.length, "matches.length");
// checkObject(response.matches, keys);
is(response.matches.join(" - "), keys.join(" - "));
}
async function doAutocompleteArray(webConsoleFront) {
info("test autocomplete for [1,2,3]");
const response = await webConsoleFront.autocomplete("[1,2,3].");
let {matches} = response;
ok(!!matches.length, "There are completion results for the array");
ok(matches.includes("length") && matches.includes("filter"),
"Array autocomplete contains expected results");
info("test autocomplete for '[] . '");
matches = (await webConsoleFront.autocomplete("[] . ")).matches;
ok(matches.length > 1);
ok(matches.includes("length") && matches.includes("filter"),
"Array autocomplete contains expected results");
ok(!matches.includes("copy"), "Array autocomplete does not contain helpers");
info("test autocomplete for '[1,2,3]['");
matches = (await webConsoleFront.autocomplete("[1,2,3][")).matches;
ok(matches.length > 1);
ok(matches.includes('"length"') && matches.includes('"filter"'),
"Array autocomplete contains expected results, surrounded by quotes");
info("test autocomplete for '[1,2,3]['");
matches = (await webConsoleFront.autocomplete("[1,2,3]['")).matches;
ok(matches.length > 1);
ok(matches.includes("'length'") && matches.includes("'filter'"),
"Array autocomplete contains expected results, surrounded by quotes");
info("test autocomplete for '[1,2,3][l");
matches = (await webConsoleFront.autocomplete("[1,2,3][l")).matches;
ok(matches.length >= 1);
ok(matches.includes('"length"'),
"Array autocomplete contains expected results, surrounded by quotes");
info("test autocomplete for '[1,2,3]['l");
matches = (await webConsoleFront.autocomplete("[1,2,3]['l")).matches;
ok(matches.length >= 1);
ok(matches.includes("'length'"),
"Array autocomplete contains expected results, surrounded by quotes");
}
async function doAutocompleteString(webConsoleFront) {
info(`test autocomplete for "foo".`);
const response = await webConsoleFront.autocomplete(`"foo".`);
let {matches} = response;
ok(!!matches.length, "There are completion results for the string");
ok(matches.includes("substr") && matches.includes("trim"),
"String autocomplete contains expected results");
info("test autocomplete for `foo`[");
matches = (await webConsoleFront.autocomplete("`foo`[")).matches;
ok(matches.length > 1, "autocomplete string with bracket works");
ok(matches.includes('"substr"') && matches.includes('"trim"'),
"String autocomplete contains expected results, surrounded by quotes");
}
async function doAutocompleteDotSurroundedBySpaces(webConsoleFront) {
info("test autocomplete for 'window.foobarObject\n .'");
let {matches} = await webConsoleFront.autocomplete("window.foobarObject\n .");
is(matches.length, 7);
checkObject(matches,
["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
info("test autocomplete for 'window.foobarObject\n .o'");
matches = (await webConsoleFront.autocomplete("window.foobarObject\n .o")).matches;
is(matches.length, 3);
checkObject(matches, ["omg", "omgfoo", "omgstr"]);
info("test autocomplete for 'window.foobarObject\n .\n s'");
matches = (await webConsoleFront.autocomplete("window.foobarObject\n .\n s")).matches;
is(matches.length, 1);
checkObject(matches, ["strfoo"]);
info("test autocomplete for 'window.foobarObject\n . '");
matches = (await webConsoleFront.autocomplete("window.foobarObject\n . ")).matches;
is(matches.length, 7);
checkObject(matches,
["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
matches =
(await webConsoleFront.autocomplete("window.foobarObject. foo ; window.foo")).matches;
is(matches.length, 1);
checkObject(matches, ["foobarObject"]);
}
async function doAutocompleteBracketSurroundedBySpaces(webConsoleFront) {
const wrap = (arr, quote = `"`) => arr.map(x => `${quote}${x}${quote}`);
let matches = await getAutocompleteMatches(webConsoleFront, "window.foobarObject\n [")
is(matches.length, 7);
checkObject(matches,
wrap(["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]));
matches = await getAutocompleteMatches(webConsoleFront, "window.foobarObject\n ['o")
is(matches.length, 3);
checkObject(matches, wrap(["omg", "omgfoo", "omgstr"], "'"));
matches = await getAutocompleteMatches(webConsoleFront, "window.foobarObject\n [\n s");
is(matches.length, 1);
checkObject(matches, [`"strfoo"`]);
matches = await getAutocompleteMatches(webConsoleFront, "window.foobarObject\n [ ");
is(matches.length, 7);
checkObject(matches,
wrap(["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]));
matches = await getAutocompleteMatches(webConsoleFront, "window.emojiObject [ '");
is(matches.length, 1);
checkObject(matches, [`'😎'`]);
}
async function doAutocompleteAfterOr(webConsoleFront) {
info("test autocomplete for 'true || foo'");
const {matches} = await webConsoleFront.autocomplete("true || foobar");
is(matches.length, 1, "autocomplete returns expected results");
is(matches.join("-"), "foobarObject");
}
async function doInsensitiveAutocomplete(webConsoleFront) {
info("test autocomplete for 'window.insensitiveTestCase.'");
let {matches} = await webConsoleFront.autocomplete("window.insensitiveTestCase.");
is(matches.join("-"), "prop-pröp-Prop-PROP-PRÖP",
"autocomplete returns the expected items, in the expected order");
info("test autocomplete for 'window.insensitiveTestCase.p'");
matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.p")).matches;
is(matches.join("-"), "prop-pröp-Prop-PROP-PRÖP",
"autocomplete is case-insensitive when first letter is lowercased");
info("test autocomplete for 'window.insensitiveTestCase.pRoP'");
matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.pRoP")).matches;
is(matches.join("-"), "prop-Prop-PROP",
"autocomplete is case-insensitive when first letter is lowercased");
info("test autocomplete for 'window.insensitiveTestCase.P'");
matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.P")).matches;
is(matches.join("-"), "Prop-PROP-PRÖP",
"autocomplete is case-sensitive when first letter is uppercased");
info("test autocomplete for 'window.insensitiveTestCase.PROP'");
matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.PROP")).matches;
is(matches.join("-"), "PROP",
"autocomplete is case-sensitive when first letter is uppercased");
info("test autocomplete for 'window.insensitiveTestCase.prö'");
matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.prö")).matches;
is(matches.join("-"), "pröp-PRÖP", "expected result with lowercase diacritic");
info("test autocomplete for 'window.insensitiveTestCase.PRÖ'");
matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.PRÖ")).matches;
is(matches.join("-"), "PRÖP", "expected result with uppercase diacritic");
}
async function doElementAccessAutocomplete(webConsoleFront) {
info("test autocomplete for 'window.elementAccessTestCase['");
let res = (await webConsoleFront.autocomplete("window.elementAccessTestCase["));
is(
res.matches.join("|"),
`"bar"|"da'ta'test"|"da\\"ta\\"test"|"da\`ta\`test"|"data-test"|"dataTest"|"BAR"`,
"autocomplete returns the expected items, wrapped in quotes");
is(res.isElementAccess, true);
info("test autocomplete for 'window.elementAccessTestCase[d'");
res = await webConsoleFront.autocomplete("window.elementAccessTestCase[d");
is(
res.matches.join("|"),
`"da'ta'test"|"da\\"ta\\"test"|"da\`ta\`test"|"data-test"|"dataTest"`,
"autocomplete returns the expected filtered items");
is(res.isElementAccess, true);
info(`test autocomplete for 'window.elementAccessTestCase["d'`);
res = await webConsoleFront.autocomplete(`window.elementAccessTestCase["d`);
is(
res.matches.join("|"),
`"da'ta'test"|"da\\"ta\\"test"|"da\`ta\`test"|"data-test"|"dataTest"`,
"autocomplete returns the expected items, wrapped in quotes");
is(res.isElementAccess, true);
info(`test autocomplete for 'window.elementAccessTestCase["data-`);
res = await webConsoleFront.autocomplete(`window.elementAccessTestCase["data-`);
is(res.matches.join("|"), `"data-test"`,
"autocomplete returns the expected items, wrapped in quotes");
is(res.isElementAccess, true);
info(`test autocomplete for 'window.elementAccessTestCase['d'`);
res = await webConsoleFront.autocomplete(`window.elementAccessTestCase['d`);
is(
res.matches.join("|"),
`'da"ta"test'|'da\\'ta\\'test'|'da\`ta\`test'|'data-test'|'dataTest'`,
"autocomplete returns the expected items, wrapped in the same quotes the user entered");
is(res.isElementAccess, true);
info("test autocomplete for 'window.elementAccessTestCase[`d'");
res = await webConsoleFront.autocomplete("window.elementAccessTestCase[`d");
is(
res.matches.join("|"),
"`da'ta'test`|`da\"ta\"test`|`da\\`ta\\`test`|`data-test`|`dataTest`",
"autocomplete returns the expected items, wrapped in the same quotes the user entered");
is(res.isElementAccess, true);
info(`test autocomplete for '['`);
res = await webConsoleFront.autocomplete(`[`);
is(res.matches, null, "it does not return anything");
info(`test autocomplete for '[1,2,3'`);
res = await webConsoleFront.autocomplete(`[1,2,3`);
is(res.matches, null, "it does not return anything");
info(`test autocomplete for '["'`);
res = await webConsoleFront.autocomplete(`["`);
is(res.matches, null, "it does not return anything");
info(`test autocomplete for '[;'`);
res = await webConsoleFront.autocomplete(`[;`);
is(res.matches, null, "it does not return anything");
}
async function doAutocompleteCommands(webConsoleFront) {
info("test autocomplete for 'c'");
let matches = (await webConsoleFront.autocomplete("c")).matches;
ok(matches.includes("clear"), "commands are returned");
info("test autocomplete for 's'");
matches = (await webConsoleFront.autocomplete("s")).matches;
is(matches.includes("screenshot"), false, "screenshot is not returned");
info("test autocomplete for ':s'");
matches = (await webConsoleFront.autocomplete(":s")).matches;
is(matches.includes(":screenshot"), true, "screenshot is returned");
info("test autocomplete for 'window.c'");
matches = (await webConsoleFront.autocomplete("window.c")).matches;
ok(!matches.includes("clear"), "commands are not returned");
info("test autocomplete for 'window[c'");
matches = (await webConsoleFront.autocomplete("window[c")).matches;
ok(!matches.includes("clear"), "commands are not returned");
info(`test autocomplete for 'window["c'`);
matches = (await webConsoleFront.autocomplete(`window["c`)).matches;
ok(!matches.includes("clear"), "commands are not returned");
info(`test autocomplete for 'window["c'`);
matches = (await webConsoleFront.autocomplete(`window["c`)).matches;
ok(!matches.includes("clear"), "commands are not returned");
info(`test autocomplete for 'window[";c'`);
matches = (await webConsoleFront.autocomplete(`window[";c`)).matches;
ok(!matches.includes("clear"), "commands are not returned");
info(`test autocomplete for 'window[;c'`);
matches = (await webConsoleFront.autocomplete(`window[;c`)).matches;
ok(!matches.includes("clear"), "commands are not returned");
}
async function doAutocompleteAfterOperator(webConsoleFront) {
const inputs = [
"true;foob",
"true,foob",
"({key:foob",
"a=foob",
"if(a<foob",
"if(a>foob",
"1+foob",
"1-foob",
"++foob",
"--foob",
"1*foob",
"2**foob",
"1/foob",
"1%foob",
"1|foob",
"1&foob",
"1^foob",
"~foob",
"1<<foob",
"1>>foob",
"1>>>foob",
"false||foob",
"false&&foob",
"x=true?foob",
"x=false?1:foob",
"!foob",
"false??foob",
];
for (const input of inputs) {
info(`test autocomplete for "${input}"`);
const matches = (await webConsoleFront.autocomplete(input)).matches;
ok(matches.includes("foobarObject"), `Expected autocomplete result for ${input}"`);
}
}
async function dontAutocompleteAfterDeclaration(webConsoleFront) {
info("test autocomplete for 'var win'");
let matches = (await webConsoleFront.autocomplete("var win")).matches;
is(matches, null, "no autocompletion on a var declaration");
info("test autocomplete for 'const win'");
matches = (await webConsoleFront.autocomplete("const win")).matches;
is(matches, null, "no autocompletion on a const declaration");
info("test autocomplete for 'let win'");
matches = (await webConsoleFront.autocomplete("let win")).matches;
is(matches, null, "no autocompletion on a let declaration");
info("test autocomplete for 'function win'");
matches = (await webConsoleFront.autocomplete("function win")).matches;
is(matches, null, "no autocompletion on a function declaration");
info("test autocomplete for 'class win'");
matches = (await webConsoleFront.autocomplete("class win")).matches;
is(matches, null, "no autocompletion on a class declaration");
info("test autocomplete for 'const win = win'");
matches = (await webConsoleFront.autocomplete("const win = win")).matches;
ok(matches.includes("window"), "autocompletion still happens after the `=` sign");
info("test autocomplete for 'in var'");
matches = (await webConsoleFront.autocomplete("in var")).matches;
ok(matches.includes("varify"),
"autocompletion still happens with a property name starting with 'var'");
}
async function doKeywordsAutocomplete(webConsoleFront) {
info("test autocomplete for 'func'");
let matches = (await webConsoleFront.autocomplete("func")).matches;
ok(matches.includes("function"), "keywords are returned");
info("test autocomplete for ':func'");
matches = (await webConsoleFront.autocomplete(":func")).matches;
is(!matches.includes("function"), true,
"'function' is not returned when prefixed with ':'");
info("test autocomplete for 'window.func'");
matches = (await webConsoleFront.autocomplete("window.func")).matches;
ok(!matches.includes("function"),
"'function' is not returned when doing a property access");
info("test autocomplete for 'window[func'");
matches = (await webConsoleFront.autocomplete("window[func")).matches;
ok(!matches.includes("function"),
"'function' is not returned when doing an element access");
}
async function dontAutocomplete(webConsoleFront) {
const inputs = [
"",
" ",
"\n",
"\n ",
" \n ",
" \n",
"true;",
"true,",
"({key:",
"a=",
"if(a<",
"if(a>",
"1+",
"1-",
"++",
"--",
"1*",
"2**",
"1/",
"1%",
"1|",
"1&",
"1^",
"~",
"1<<",
"1>>",
"1>>>",
"false||",
"false&&",
"x=true?",
"x=false?1:",
"!",
...RESERVED_JS_KEYWORDS.map(keyword => `${keyword} `),
...RESERVED_JS_KEYWORDS.map(keyword => `${keyword} `),
];
for (const input of inputs) {
info(`test autocomplete for "${input}"`);
const matches = (await webConsoleFront.autocomplete(input)).matches;
is(matches, null, `No autocomplete result for ${input}"`);
}
}
async function getAutocompleteMatches(webConsoleFront, input) {
info(`test autocomplete for "${input}"`);
const res = (await webConsoleFront.autocomplete(input));
return res.matches;
}
async function evaluateExpression(consoleFront, expression) {
const onEvaluationResult = consoleFront.once("evaluationResult");
await consoleFront.evaluateJSAsync({ text: expression });
return onEvaluationResult;
}
</script>
</body>
</html>