summaryrefslogtreecommitdiffstats
path: root/devtools/shared/commands/script/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /devtools/shared/commands/script/tests
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--devtools/shared/commands/script/tests/browser.ini11
-rw-r--r--devtools/shared/commands/script/tests/browser_script_command_execute_basic.js413
-rw-r--r--devtools/shared/commands/script/tests/browser_script_command_execute_document__proto__.js41
-rw-r--r--devtools/shared/commands/script/tests/browser_script_command_execute_last_result.js85
-rw-r--r--devtools/shared/commands/script/tests/browser_script_command_execute_throw.js75
-rw-r--r--devtools/shared/commands/script/tests/head.js46
6 files changed, 671 insertions, 0 deletions
diff --git a/devtools/shared/commands/script/tests/browser.ini b/devtools/shared/commands/script/tests/browser.ini
new file mode 100644
index 0000000000..949c54f760
--- /dev/null
+++ b/devtools/shared/commands/script/tests/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ !/devtools/client/shared/test/shared-head.js
+ head.js
+
+[browser_script_command_execute_basic.js]
+[browser_script_command_execute_document__proto__.js]
+[browser_script_command_execute_last_result.js]
+[browser_script_command_execute_throw.js]
diff --git a/devtools/shared/commands/script/tests/browser_script_command_execute_basic.js b/devtools/shared/commands/script/tests/browser_script_command_execute_basic.js
new file mode 100644
index 0000000000..5eff8c4520
--- /dev/null
+++ b/devtools/shared/commands/script/tests/browser_script_command_execute_basic.js
@@ -0,0 +1,413 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"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,
+ <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];
+ }
+ </script>`);
+
+ 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 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",
+ },
+ ];
+
+ for (const { code, result } of testData) {
+ 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",
+ });
+}
diff --git a/devtools/shared/commands/script/tests/browser_script_command_execute_document__proto__.js b/devtools/shared/commands/script/tests/browser_script_command_execute_document__proto__.js
new file mode 100644
index 0000000000..28f56ebac3
--- /dev/null
+++ b/devtools/shared/commands/script/tests/browser_script_command_execute_document__proto__.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing evaluating document.__proto__
+
+add_task(async () => {
+ const tab = await addTab(
+ `data:text/html;charset=utf-8,Test evaluating document.__proto__`
+ );
+ const commands = await CommandsFactory.forTab(tab);
+ await commands.targetCommand.startListening();
+
+ const evaluationResponse = await commands.scriptCommand.execute(
+ "document.__proto__"
+ );
+ checkObject(evaluationResponse, {
+ input: "document.__proto__",
+ result: {
+ type: "object",
+ actor: /[a-z]/,
+ },
+ });
+
+ ok(!evaluationResponse.exception, "no eval exception");
+ ok(!evaluationResponse.helperResult, "no helper result");
+
+ const response = await evaluationResponse.result.getPrototypeAndProperties();
+ ok(!response.error, "no response error");
+
+ const props = response.ownProperties;
+ ok(props, "response properties available");
+
+ const expectedProps = Object.getOwnPropertyNames(
+ Object.getPrototypeOf(document)
+ );
+ checkObject(Object.keys(props), expectedProps, "Same own properties.");
+
+ await commands.destroy();
+});
diff --git a/devtools/shared/commands/script/tests/browser_script_command_execute_last_result.js b/devtools/shared/commands/script/tests/browser_script_command_execute_last_result.js
new file mode 100644
index 0000000000..aebdaeb168
--- /dev/null
+++ b/devtools/shared/commands/script/tests/browser_script_command_execute_last_result.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing that last evaluation result can be accessed with `$_`
+
+add_task(async () => {
+ const tab = await addTab(`data:text/html;charset=utf-8,`);
+
+ const commands = await CommandsFactory.forTab(tab);
+ await commands.targetCommand.startListening();
+
+ info("$_ returns undefined if nothing has evaluated yet");
+ let response = await commands.scriptCommand.execute("$_");
+ basicResultCheck(response, "$_", { type: "undefined" });
+
+ info("$_ returns last value and performs basic arithmetic");
+ response = await commands.scriptCommand.execute("2+2");
+ basicResultCheck(response, "2+2", 4);
+
+ response = await commands.scriptCommand.execute("$_");
+ basicResultCheck(response, "$_", 4);
+
+ response = await commands.scriptCommand.execute("$_ + 2");
+ basicResultCheck(response, "$_ + 2", 6);
+
+ response = await commands.scriptCommand.execute("$_ + 4");
+ basicResultCheck(response, "$_ + 4", 10);
+
+ info("$_ has correct references to objects");
+ response = await commands.scriptCommand.execute("var foo = {bar:1}; foo;");
+ basicResultCheck(response, "var foo = {bar:1}; foo;", {
+ type: "object",
+ class: "Object",
+ actor: /[a-z]/,
+ });
+ checkObject(response.result.getGrip().preview.ownProperties, {
+ bar: {
+ value: 1,
+ },
+ });
+
+ response = await commands.scriptCommand.execute("$_");
+ basicResultCheck(response, "$_", {
+ type: "object",
+ class: "Object",
+ actor: /[a-z]/,
+ });
+ checkObject(response.result.getGrip().preview.ownProperties, {
+ bar: {
+ value: 1,
+ },
+ });
+
+ info(
+ "Update a property value and check that evaluating $_ returns the expected object instance"
+ );
+ await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+ content.wrappedJSObject.foo.bar = "updated_value";
+ });
+
+ response = await commands.scriptCommand.execute("$_");
+ basicResultCheck(response, "$_", {
+ type: "object",
+ class: "Object",
+ actor: /[a-z]/,
+ });
+ checkObject(response.result.getGrip().preview.ownProperties, {
+ bar: {
+ value: "updated_value",
+ },
+ });
+
+ await commands.destroy();
+});
+
+function basicResultCheck(response, input, output) {
+ checkObject(response, {
+ input,
+ result: output,
+ });
+ ok(!response.exception, "no eval exception");
+ ok(!response.helperResult, "no helper result");
+}
diff --git a/devtools/shared/commands/script/tests/browser_script_command_execute_throw.js b/devtools/shared/commands/script/tests/browser_script_command_execute_throw.js
new file mode 100644
index 0000000000..8680193ecb
--- /dev/null
+++ b/devtools/shared/commands/script/tests/browser_script_command_execute_throw.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing evaluating thowing expressions
+const {
+ DevToolsServer,
+} = require("resource://devtools/server/devtools-server.js");
+
+add_task(async () => {
+ const tab = await addTab(`data:text/html;charset=utf-8,Test throw`);
+ const commands = await CommandsFactory.forTab(tab);
+ await commands.targetCommand.startListening();
+
+ const falsyValues = [
+ "-0",
+ "null",
+ "undefined",
+ "Infinity",
+ "-Infinity",
+ "NaN",
+ ];
+ for (const value of falsyValues) {
+ const response = await commands.scriptCommand.execute(`throw ${value};`);
+ is(
+ response.exception.type,
+ value,
+ `Got the expected value for response.exception.type when throwing "${value}"`
+ );
+ }
+
+ const identityTestValues = [false, 0];
+ for (const value of identityTestValues) {
+ const response = await commands.scriptCommand.execute(`throw ${value};`);
+ is(
+ response.exception,
+ value,
+ `Got the expected value for response.exception when throwing "${value}"`
+ );
+ }
+
+ const symbolTestValues = [
+ ["Symbol.iterator", "Symbol(Symbol.iterator)"],
+ ["Symbol('foo')", "Symbol(foo)"],
+ ["Symbol()", "Symbol()"],
+ ];
+ for (const [expr, message] of symbolTestValues) {
+ const response = await commands.scriptCommand.execute(`throw ${expr};`);
+ is(
+ response.exceptionMessage,
+ message,
+ `Got the expected value for response.exceptionMessage when throwing "${expr}"`
+ );
+ }
+
+ const longString = Array(DevToolsServer.LONG_STRING_LENGTH + 1).join("a"),
+ shortedString = longString.substring(
+ 0,
+ DevToolsServer.LONG_STRING_INITIAL_LENGTH
+ );
+ const response = await commands.scriptCommand.execute(
+ "throw '" + longString + "';"
+ );
+ is(
+ response.exception.initial,
+ shortedString,
+ "Got the expected value for exception.initial when throwing a longString"
+ );
+ is(
+ response.exceptionMessage.initial,
+ shortedString,
+ "Got the expected value for exceptionMessage.initial when throwing a longString"
+ );
+});
diff --git a/devtools/shared/commands/script/tests/head.js b/devtools/shared/commands/script/tests/head.js
new file mode 100644
index 0000000000..9b7591a9cf
--- /dev/null
+++ b/devtools/shared/commands/script/tests/head.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from ../../../../client/shared/test/shared-head.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+ this
+);
+
+function checkObject(object, expected) {
+ if (object && object.getGrip) {
+ object = object.getGrip();
+ }
+
+ for (const name of Object.keys(expected)) {
+ const expectedValue = expected[name];
+ const value = object[name];
+ checkValue(name, value, expectedValue);
+ }
+}
+
+function checkValue(name, value, expected) {
+ if (expected === null) {
+ is(value, null, `'${name}' is null`);
+ } else if (expected === undefined) {
+ is(value, expected, `'${name}' is undefined`);
+ } else if (
+ typeof expected == "string" ||
+ typeof expected == "number" ||
+ typeof expected == "boolean"
+ ) {
+ is(value, expected, "property '" + name + "'");
+ } else if (expected instanceof RegExp) {
+ ok(expected.test(value), name + ": " + expected + " matched " + value);
+ } else if (Array.isArray(expected)) {
+ info("checking array for property '" + name + "'");
+ checkObject(value, expected);
+ } else if (typeof expected == "object") {
+ info("checking object for property '" + name + "'");
+ checkObject(value, expected);
+ }
+}