Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"use strict";
// Testing basic expression evaluation
const {
MAX_AUTOCOMPLETE_ATTEMPTS,
MAX_AUTOCOMPLETIONS,
} = require("resource://devtools/shared/webconsole/js-property-provider.js");
const {
DevToolsServer,
} = require("resource://devtools/server/devtools-server.js");
add_task(async () => {
const tab = await addTab(`data:text/html;charset=utf-8,
<!DOCTYPE html>
<html dir="ltr" class="class1">
<head><title>Testcase</title></head>
<script>
window.foobarObject = Object.create(
null,
Object.getOwnPropertyDescriptors({
foo: 1,
foobar: 2,
foobaz: 3,
omg: 4,
omgfoo: 5,
strfoo: "foobarz",
omgstr: "foobarz" + "abb".repeat(${DevToolsServer.LONG_STRING_LENGTH} * 2),
})
);
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;
}
var originalExec = RegExp.prototype.exec;
var promptIterable = { [Symbol.iterator]() { return { next: prompt } } };
function aliasedTest() {
const aliased = "ALIASED";
return [0].map(() => aliased)[0];
}
var testMap = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
var testSet = new Set([1, 2, 3, 4, 5]);
var testProxy = new Proxy({}, { getPrototypeOf: prompt });
var testArray = [1,2,3];
var testInt8Array = new Int8Array([1, 2, 3]);
var testArrayBuffer = testInt8Array.buffer;
var testDataView = new DataView(testArrayBuffer, 2);
var testCanvasContext = document.createElement("canvas").getContext("2d");
var objWithNativeGetter = {};
Object.defineProperty(objWithNativeGetter, "print", { get: print });
Object.defineProperty(objWithNativeGetter, "Element", { get: Element });
Object.defineProperty(objWithNativeGetter, "setAttribute", { get: Element.prototype.setAttribute });
Object.defineProperty(objWithNativeGetter, "setClassName", { get: Object.getOwnPropertyDescriptor(Element.prototype, "className").set });
Object.defineProperty(objWithNativeGetter, "requestPermission", { get: Notification.requestPermission });
async function testAsync() { return 10; }
async function testAsyncAwait() { await 1; return 10; }
async function * testAsyncGen() { return 10; }
async function * testAsyncGenAwait() { await 1; return 10; }
function testFunc() {}
var testLocale = new Intl.Locale("de-latn-de-u-ca-gregory-co-phonebk-hc-h23-kf-true-kn-false-nu-latn");
var testFormData = new FormData();
var testHeaders = new Headers();
var testURLSearchParams = new URLSearchParams();
var testReadableStream = new ReadableStream();
</script>
<body id="body1" class="class2"><h1>Body text</h1></body>
</html>`);
const commands = await CommandsFactory.forTab(tab);
await commands.targetCommand.startListening();
await doSimpleEval(commands);
await doWindowEval(commands);
await doEvalWithException(commands);
await doEvalWithHelper(commands);
await doEvalString(commands);
await doEvalLongString(commands);
await doEvalWithBinding(commands);
await forceLexicalInit(commands);
await doSimpleEagerEval(commands);
await doEagerEvalWithSideEffect(commands);
await doEagerEvalWithSideEffectIterator(commands);
await doEagerEvalWithSideEffectMonkeyPatched(commands);
await doEagerEvalESGetters(commands);
await doEagerEvalDOMGetters(commands);
await doEagerEvalOtherNativeGetters(commands);
await doEagerEvalDOMMethods(commands);
await doEagerEvalAsyncFunctions(commands);
await commands.destroy();
});
async function doSimpleEval(commands) {
info("test eval '2+2'");
const response = await commands.scriptCommand.execute("2+2");
checkObject(response, {
input: "2+2",
result: 4,
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
async function doWindowEval(commands) {
info("test eval 'document'");
const response = await commands.scriptCommand.execute("document");
checkObject(response, {
input: "document",
result: {
type: "object",
class: "HTMLDocument",
actor: /[a-z]/,
},
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
async function doEvalWithException(commands) {
info("test eval with exception");
const response = await commands.scriptCommand.execute(
"window.doTheImpossible()"
);
checkObject(response, {
input: "window.doTheImpossible()",
result: {
type: "undefined",
},
exceptionMessage: /doTheImpossible/,
});
ok(response.exception, "js eval exception");
ok(!response.helperResult, "no helper result");
}
async function doEvalWithHelper(commands) {
info("test eval with helper");
const response = await commands.scriptCommand.execute("clear()");
checkObject(response, {
input: "clear()",
result: {
type: "undefined",
},
helperResult: { type: "clearOutput" },
});
ok(!response.exception, "no eval exception");
}
async function doEvalString(commands) {
const response = await commands.scriptCommand.execute(
"window.foobarObject.strfoo"
);
checkObject(response, {
input: "window.foobarObject.strfoo",
result: "foobarz",
});
}
async function doEvalLongString(commands) {
const response = await commands.scriptCommand.execute(
"window.foobarObject.omgstr"
);
const str = await SpecialPowers.spawn(
gBrowser.selectedBrowser,
[],
function () {
return content.wrappedJSObject.foobarObject.omgstr;
}
);
const initial = str.substring(0, DevToolsServer.LONG_STRING_INITIAL_LENGTH);
checkObject(response, {
input: "window.foobarObject.omgstr",
result: {
type: "longString",
initial,
length: str.length,
},
});
}
async function doEvalWithBinding(commands) {
const response = await commands.scriptCommand.execute("document;");
const documentActor = response.result.actorID;
info("running a command with _self as document using selectedObjectActor");
const selectedObjectSame = await commands.scriptCommand.execute(
"_self === document",
{
selectedObjectActor: documentActor,
}
);
checkObject(selectedObjectSame, {
result: true,
});
}
async function forceLexicalInit(commands) {
info("test that failed let/const bindings are initialized to undefined");
const testData = [
{
stmt: "let foopie = wubbalubadubdub",
vars: ["foopie"],
},
{
stmt: "let {z, w={n}=null} = {}",
vars: ["z", "w"],
},
{
stmt: "let [a, b, c] = null",
vars: ["a", "b", "c"],
},
{
stmt: "const nein1 = rofl, nein2 = copter",
vars: ["nein1", "nein2"],
},
{
stmt: "const {ha} = null",
vars: ["ha"],
},
{
stmt: "const [haw=[lame]=null] = []",
vars: ["haw"],
},
{
stmt: "const [rawr, wat=[lame]=null] = []",
vars: ["rawr", "haw"],
},
{
stmt: "let {zzz: xyz=99, zwz: wb} = nexistepas()",
vars: ["xyz", "wb"],
},
{
stmt: "let {c3pdoh=101} = null",
vars: ["c3pdoh"],
},
{
stmt: "const {...x} = x",
vars: ["x"],
},
{
stmt: "const {xx,yy,...rest} = null",
vars: ["xx", "yy", "rest"],
},
];
for (const data of testData) {
const response = await commands.scriptCommand.execute(data.stmt);
checkObject(response, {
input: data.stmt,
result: { type: "undefined" },
});
ok(response.exception, "expected exception");
for (const varName of data.vars) {
const response2 = await commands.scriptCommand.execute(varName);
checkObject(response2, {
input: varName,
result: { type: "undefined" },
});
ok(!response2.exception, "unexpected exception");
}
}
}
async function doSimpleEagerEval(commands) {
const testData = [
{
code: "2+2",
result: 4,
},
{
code: "(x => x * 2)(3)",
result: 6,
},
{
code: "[1, 2, 3].map(x => x * 2).join()",
result: "2,4,6",
},
{
code: `"abc".match(/a./)[0]`,
result: "ab",
},
{
code: "aliasedTest()",
result: "ALIASED",
},
{
code: "testArray.concat([4,5]).join()",
result: "1,2,3,4,5",
},
{
code: "testArray.entries().toString()",
result: "[object Array Iterator]",
},
{
code: "testArray.keys().toString()",
result: "[object Array Iterator]",
},
{
code: "testArray.values().toString()",
result: "[object Array Iterator]",
},
{
code: "testArray.every(x => x < 100)",
result: true,
},
{
code: "testArray.some(x => x > 1)",
result: true,
},
{
code: "testArray.filter(x => x % 2 == 0).join()",
result: "2",
},
{
code: "testArray.find(x => x % 2 == 0)",
result: 2,
},
{
code: "testArray.findIndex(x => x % 2 == 0)",
result: 1,
},
{
code: "[testArray].flat().join()",
result: "1,2,3",
},
{
code: "[testArray].flatMap(x => x).join()",
result: "1,2,3",
},
{
code: "testArray.forEach(x => x); testArray.join()",
result: "1,2,3",
},
{
code: "testArray.includes(1)",
result: true,
},
{
code: "testArray.lastIndexOf(1)",
result: 0,
},
{
code: "testArray.map(x => x + 1).join()",
result: "2,3,4",
},
{
code: "testArray.reduce((acc,x) => acc + x, 0)",
result: 6,
},
{
code: "testArray.reduceRight((acc,x) => acc + x, 0)",
result: 6,
},
{
code: "testArray.slice(0,1).join()",
result: "1",
},
{
code: "testArray.toReversed().join()",
result: "3,2,1",
},
{
code: "testArray.toSorted().join()",
result: "1,2,3",
},
{
code: "testArray.toSpliced(0,1).join()",
result: "2,3",
},
{
code: "testArray.with(1, 'b').join()",
result: "1,b,3",
},
{
code: "testInt8Array.entries().toString()",
result: "[object Array Iterator]",
},
{
code: "testInt8Array.keys().toString()",
result: "[object Array Iterator]",
},
{
code: "testInt8Array.values().toString()",
result: "[object Array Iterator]",
},
{
code: "testInt8Array.every(x => x < 100)",
result: true,
},
{
code: "testInt8Array.some(x => x > 1)",
result: true,
},
{
code: "testInt8Array.filter(x => x % 2 == 0).join()",
result: "2",
},
{
code: "testInt8Array.find(x => x % 2 == 0)",
result: 2,
},
{
code: "testInt8Array.findIndex(x => x % 2 == 0)",
result: 1,
},
{
code: "testInt8Array.forEach(x => x); testInt8Array.join()",
result: "1,2,3",
},
{
code: "testInt8Array.includes(1)",
result: true,
},
{
code: "testInt8Array.lastIndexOf(1)",
result: 0,
},
{
code: "testInt8Array.map(x => x + 1).join()",
result: "2,3,4",
},
{
code: "testInt8Array.reduce((acc,x) => acc + x, 0)",
result: 6,
},
{
code: "testInt8Array.reduceRight((acc,x) => acc + x, 0)",
result: 6,
},
{
code: "testInt8Array.slice(0,1).join()",
result: "1",
},
{
code: "testInt8Array.toReversed().join()",
skip:
typeof Reflect.getPrototypeOf(Int8Array).prototype.toReversed !==
"function",
result: "3,2,1",
},
{
code: "testInt8Array.toSorted().join()",
skip:
typeof Reflect.getPrototypeOf(Int8Array).prototype.toSorted !==
"function",
result: "1,2,3",
},
{
code: "testInt8Array.with(1, 0).join()",
skip:
typeof Reflect.getPrototypeOf(Int8Array).prototype.with !== "function",
result: "1,0,3",
},
];
for (const { code, result, skip } of testData) {
if (skip) {
info(`Skipping evaluation of ${code}`);
continue;
}
info(`Evaluating: ${code}`);
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(response, {
input: code,
result,
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
}
async function doEagerEvalWithSideEffect(commands) {
const testData = [
// Modify environment.
"var a = 10; a;",
// Directly call a funtion with side effect.
"prompt();",
// Call a funtion with side effect inside a scripted function.
"(() => { prompt(); })()",
// Call a funtion with side effect from self-hosted JS function.
"[1, 2, 3].map(prompt)",
// Call a function with Function.prototype.call.
"Function.prototype.call.bind(Function.prototype.call)(prompt);",
// Call a function with Function.prototype.apply.
"Function.prototype.apply.bind(Function.prototype.apply)(prompt);",
// Indirectly call a function with Function.prototype.apply.
"Reflect.apply(prompt, null, []);",
"'aaaaaaaa'.replace(/(a)(a)(a)(a)(a)(a)(a)(a)/, prompt)",
// Indirect call on obj[Symbol.iterator]().next.
"Array.from(promptIterable)",
];
for (const code of testData) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(response, {
input: code,
result: { type: "undefined" },
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
}
async function doEagerEvalWithSideEffectIterator(commands) {
// Indirect call on %ArrayIterator%.prototype.next,
// Create an iterable object that reuses iterator across multiple call.
let response = await commands.scriptCommand.execute(`
var arr = [1, 2, 3];
var iterator = arr[Symbol.iterator]();
var iterable = { [Symbol.iterator]() { return iterator; } };
"ok";
`);
checkObject(response, {
result: "ok",
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
const testData = [
"Array.from(iterable)",
"new Map(iterable)",
"new Set(iterable)",
];
for (const code of testData) {
response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(response, {
input: code,
result: { type: "undefined" },
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
// Verify the iterator's internal state isn't modified.
response = await commands.scriptCommand.execute(`[...iterator].join(",")`);
checkObject(response, {
result: "1,2,3",
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
async function doEagerEvalWithSideEffectMonkeyPatched(commands) {
// Patch the built-in function without eager evaluation.
let response = await commands.scriptCommand.execute(
`RegExp.prototype.exec = prompt; "patched"`
);
checkObject(response, {
result: "patched",
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
// Test eager evaluation, where the patched built-in is called internally.
// This should be aborted.
const code = `"abc".match(/a./)[0]`;
response = await commands.scriptCommand.execute(code, { eager: true });
checkObject(response, {
input: code,
result: { type: "undefined" },
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
// Undo the patch without eager evaluation.
response = await commands.scriptCommand.execute(
`RegExp.prototype.exec = originalExec; "unpatched"`
);
checkObject(response, {
result: "unpatched",
});
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
// Test eager evaluation again, without the patch.
// This should be evaluated.
response = await commands.scriptCommand.execute(code, { eager: true });
checkObject(response, {
input: code,
result: "ab",
});
}
async function doEagerEvalESGetters(commands) {
// [code, expectedResult]
const testData = [
// ArrayBuffer
["testArrayBuffer.byteLength", 3],
// DataView
["testDataView.buffer === testArrayBuffer", true],
["testDataView.byteLength", 1],
["testDataView.byteOffset", 2],
// Error
["typeof new Error().stack", "string"],
// Function
["typeof testFunc.arguments", "object"],
["typeof testFunc.caller", "object"],
// Intl.Locale
["testLocale.baseName", "de-Latn-DE"],
["testLocale.calendar", "gregory"],
["testLocale.caseFirst", ""],
["testLocale.collation", "phonebk"],
["testLocale.hourCycle", "h23"],
["testLocale.numeric", false],
["testLocale.numberingSystem", "latn"],
["testLocale.language", "de"],
["testLocale.script", "Latn"],
["testLocale.region", "DE"],
// Map
["testMap.size", 4],
// RegExp
["/a/.dotAll", false],
["/a/giy.flags", "giy"],
["/a/g.global", true],
["/a/g.hasIndices", false],
["/a/g.ignoreCase", false],
["/a/g.multiline", false],
["/a/g.source", "a"],
["/a/g.sticky", false],
["/a/g.unicode", false],
// Set
["testSet.size", 5],
// Symbol
["Symbol.iterator.description", "Symbol.iterator"],
// TypedArray
["testInt8Array.buffer === testArrayBuffer", true],
["testInt8Array.byteLength", 3],
["testInt8Array.byteOffset", 0],
["testInt8Array.length", 3],
["testInt8Array[Symbol.toStringTag]", "Int8Array"],
];
for (const [code, expectedResult] of testData) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: expectedResult,
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
// Test RegExp static properties.
// Run preparation code here to avoid interference with other tests,
// given RegExp static properties are global state.
const regexpPreparationCode = `
/b(c)(d)(e)(f)(g)(h)(i)(j)(k)l/.test("abcdefghijklm")
`;
const prepResponse = await commands.scriptCommand.execute(
regexpPreparationCode
);
checkObject(prepResponse, {
input: regexpPreparationCode,
result: true,
});
ok(!prepResponse.exception, "no eval exception");
ok(!prepResponse.helperResult, "no helper result");
const testDataRegExp = [
// RegExp static
["RegExp.input", "abcdefghijklm"],
["RegExp.lastMatch", "bcdefghijkl"],
["RegExp.lastParen", "k"],
["RegExp.leftContext", "a"],
["RegExp.rightContext", "m"],
["RegExp.$1", "c"],
["RegExp.$2", "d"],
["RegExp.$3", "e"],
["RegExp.$4", "f"],
["RegExp.$5", "g"],
["RegExp.$6", "h"],
["RegExp.$7", "i"],
["RegExp.$8", "j"],
["RegExp.$9", "k"],
["RegExp.$_", "abcdefghijklm"], // input
["RegExp['$&']", "bcdefghijkl"], // lastMatch
["RegExp['$+']", "k"], // lastParen
["RegExp['$`']", "a"], // leftContext
["RegExp[`$'`]", "m"], // rightContext
];
for (const [code, expectedResult] of testDataRegExp) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: expectedResult,
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
const testDataWithSideEffect = [
// get Object.prototype.__proto__
//
// This can invoke Proxy getPrototypeOf handler, which can be any native
// function, and debugger cannot hook the call.
`[].__proto__`,
`testProxy.__proto__`,
];
for (const code of testDataWithSideEffect) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: { type: "undefined" },
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
}
async function doEagerEvalDOMGetters(commands) {
// Getters explicitly marked no-side-effect.
//
// [code, expectedResult]
const testDataExplicit = [
// DOMTokenList
["document.documentElement.classList.length", 1],
["document.documentElement.classList.value", "class1"],
// Document
["document.URL.startsWith('data:')", true],
["document.documentURI.startsWith('data:')", true],
["document.compatMode", "CSS1Compat"],
["document.characterSet", "UTF-8"],
["document.charset", "UTF-8"],
["document.inputEncoding", "UTF-8"],
["document.contentType", "text/html"],
["document.doctype.constructor.name", "DocumentType"],
["document.documentElement.constructor.name", "HTMLHtmlElement"],
["document.title", "Testcase"],
["document.dir", "ltr"],
["document.body.constructor.name", "HTMLBodyElement"],
["document.head.constructor.name", "HTMLHeadElement"],
["document.images.constructor.name", "HTMLCollection"],
["document.embeds.constructor.name", "HTMLCollection"],
["document.plugins.constructor.name", "HTMLCollection"],
["document.links.constructor.name", "HTMLCollection"],
["document.forms.constructor.name", "HTMLCollection"],
["document.scripts.constructor.name", "HTMLCollection"],
["document.defaultView === window", true],
["typeof document.currentScript", "object"],
["document.anchors.constructor.name", "HTMLCollection"],
["document.applets.constructor.name", "HTMLCollection"],
["document.all.constructor.name", "HTMLAllCollection"],
["document.styleSheetSets.constructor.name", "DOMStringList"],
["typeof document.featurePolicy", "undefined"],
["typeof document.blockedNodeByClassifierCount", "undefined"],
["typeof document.blockedNodesByClassifier", "undefined"],
["typeof document.permDelegateHandler", "undefined"],
["document.children.constructor.name", "HTMLCollection"],
["document.firstElementChild === document.documentElement", true],
["document.lastElementChild === document.documentElement", true],
["document.childElementCount", 1],
["document.location.href.startsWith('data:')", true],
// Element
["document.body.namespaceURI", "http://www.w3.org/1999/xhtml"],
["document.body.prefix === null", true],
["document.body.localName", "body"],
["document.body.tagName", "BODY"],
["document.body.id", "body1"],
["document.body.className", "class2"],
["document.body.classList.constructor.name", "DOMTokenList"],
["document.body.part.constructor.name", "DOMTokenList"],
["document.body.attributes.constructor.name", "NamedNodeMap"],
["document.body.innerHTML.includes('Body text')", true],
["document.body.outerHTML.includes('Body text')", true],
["document.body.previousElementSibling !== null", true],
["document.body.nextElementSibling === null", true],
["document.body.children.constructor.name", "HTMLCollection"],
["document.body.firstElementChild !== null", true],
["document.body.lastElementChild !== null", true],
["document.body.childElementCount", 1],
// Node
["document.body.nodeType === Node.ELEMENT_NODE", true],
["document.body.nodeName", "BODY"],
["document.body.baseURI.startsWith('data:')", true],
["document.body.isConnected", true],
["document.body.ownerDocument === document", true],
["document.body.parentNode === document.documentElement", true],
["document.body.parentElement === document.documentElement", true],
["document.body.childNodes.constructor.name", "NodeList"],
["document.body.firstChild !== null", true],
["document.body.lastChild !== null", true],
["document.body.previousSibling !== null", true],
["document.body.nextSibling === null", true],
["document.body.nodeValue === null", true],
["document.body.textContent.includes('Body text')", true],
["typeof document.body.flattenedTreeParentNode", "undefined"],
["typeof document.body.isNativeAnonymous", "undefined"],
["typeof document.body.containingShadowRoot", "undefined"],
["typeof document.body.accessibleNode", "undefined"],
// Performance
["performance.timeOrigin > 0", true],
["performance.timing.constructor.name", "PerformanceTiming"],
["performance.navigation.constructor.name", "PerformanceNavigation"],
["performance.eventCounts.constructor.name", "EventCounts"],
// window
["window.window === window", true],
["window.self === window", true],
["window.document.constructor.name", "HTMLDocument"],
["window.performance.constructor.name", "Performance"],
["typeof window.browsingContext", "undefined"],
["typeof window.windowUtils", "undefined"],
["typeof window.windowGlobalChild", "undefined"],
["window.visualViewport.constructor.name", "VisualViewport"],
["typeof window.caches", "undefined"],
["window.location.href.startsWith('data:')", true],
];
if (typeof Scheduler === "function") {
// Scheduler is behind a pref.
testDataExplicit.push(["window.scheduler.constructor.name", "Scheduler"]);
}
for (const [code, expectedResult] of testDataExplicit) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: expectedResult,
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
// Getters not-explicitly marked no-side-effect.
// All DOM getters are considered no-side-effect in eager evaluation context.
const testDataImplicit = [
// NOTE: This is not an exhaustive list.
// Document
[`document.implementation.constructor.name`, "DOMImplementation"],
[`typeof document.domain`, "string"],
[`typeof document.referrer`, "string"],
[`typeof document.cookie`, "string"],
[`typeof document.lastModified`, "string"],
[`typeof document.readyState`, "string"],
[`typeof document.designMode`, "string"],
[`typeof document.onbeforescriptexecute`, "object"],
[`typeof document.onafterscriptexecute`, "object"],
// Element
[`typeof document.documentElement.scrollTop`, "number"],
[`typeof document.documentElement.scrollLeft`, "number"],
[`typeof document.documentElement.scrollWidth`, "number"],
[`typeof document.documentElement.scrollHeight`, "number"],
// Performance
[`typeof performance.onresourcetimingbufferfull`, "object"],
// window
[`typeof window.name`, "string"],
[`window.history.constructor.name`, "History"],
[`window.customElements.constructor.name`, "CustomElementRegistry"],
[`window.locationbar.constructor.name`, "BarProp"],
[`window.menubar.constructor.name`, "BarProp"],
[`typeof window.status`, "string"],
[`window.closed`, false],
// CanvasRenderingContext2D / CanvasCompositing
[`testCanvasContext.globalAlpha`, 1],
];
for (const [code, expectedResult] of testDataImplicit) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: expectedResult,
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
}
async function doEagerEvalOtherNativeGetters(commands) {
// DOM getter functions are allowed to be eagerly-evaluated.
// Test the situation where non-DOM-getter function is called by accessing
// getter.
//
// "being a DOM getter" is tested by checking if the native function has
// JSJitInfo and it's marked as getter.
const testData = [
// Has no JitInfo.
"objWithNativeGetter.print",
"objWithNativeGetter.Element",
// Not marked as getter, but method.
"objWithNativeGetter.getAttribute",
// Not marked as getter, but setter.
"objWithNativeGetter.setClassName",
// Not marked as getter, but static method.
"objWithNativeGetter.requestPermission",
];
for (const code of testData) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: { type: "undefined" },
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
}
async function doEagerEvalDOMMethods(commands) {
// The following "values" methods share single native function with different
// JitInfo, while ReadableStream's "values" isn't side-effect free.
// TODO: See Bug 1910717, this can be removed when we remove the pref for iterator helpers
let constructorName = "Object";
if (typeof Iterator === "function") {
constructorName = "Iterator";
}
const testDataAllowed = [
[`testFormData.values().constructor.name`, constructorName],
[`testHeaders.values().constructor.name`, constructorName],
[`testURLSearchParams.values().constructor.name`, constructorName],
];
for (const [code, expectedResult] of testDataAllowed) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: expectedResult,
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
const testDataDisallowed = ["testReadableStream.values()"];
for (const code of testDataDisallowed) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: { type: "undefined" },
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
}
async function doEagerEvalAsyncFunctions(commands) {
// [code, expectedResult]
const testData = [["typeof testAsync()", "object"]];
for (const [code, expectedResult] of testData) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: expectedResult,
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
const testDataWithSideEffect = [
// await is effectful
"testAsyncAwait()",
// initial yield is effectful
"testAsyncGen()",
"testAsyncGenAwait()",
];
for (const code of testDataWithSideEffect) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: { type: "undefined" },
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
}