From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../shared/webconsole/test/browser/browser.ini | 13 + .../test/browser/browser_commands_registration.js | 78 +++ .../test/browser/browser_network_longstring.js | 183 +++++ devtools/shared/webconsole/test/browser/data.json | 3 + .../webconsole/test/browser/data.json^headers^ | 3 + devtools/shared/webconsole/test/browser/head.js | 61 ++ .../test/browser/network_requests_iframe.html | 66 ++ devtools/shared/webconsole/test/chrome/chrome.ini | 34 + devtools/shared/webconsole/test/chrome/common.js | 274 ++++++++ .../webconsole/test/chrome/console-test-worker.js | 21 + devtools/shared/webconsole/test/chrome/data.json | 5 + .../webconsole/test/chrome/data.json^headers^ | 3 + .../webconsole/test/chrome/helper_serviceworker.js | 21 + .../test/chrome/network_requests_iframe.html | 66 ++ .../webconsole/test/chrome/sandboxed_iframe.html | 8 + .../shared/webconsole/test/chrome/test_basics.html | 61 ++ .../test/chrome/test_cached_messages.html | 217 ++++++ .../test/chrome/test_console_assert.html | 106 +++ .../test/chrome/test_console_group_styling.html | 121 ++++ .../test/chrome/test_console_serviceworker.html | 202 ++++++ .../chrome/test_console_serviceworker_cached.html | 119 ++++ .../test/chrome/test_console_styling.html | 134 ++++ .../test/chrome/test_console_timestamp.html | 48 ++ .../test/chrome/test_console_worker.html | 73 ++ .../webconsole/test/chrome/test_consoleapi.html | 225 +++++++ .../test/chrome/test_consoleapi_innerID.html | 157 +++++ .../webconsole/test/chrome/test_file_uri.html | 110 +++ .../test/chrome/test_jsterm_autocomplete.html | 635 ++++++++++++++++++ .../webconsole/test/chrome/test_network_get.html | 132 ++++ .../webconsole/test/chrome/test_network_post.html | 143 ++++ .../test/chrome/test_network_security-hsts.html | 89 +++ .../test/chrome/test_nsiconsolemessage.html | 74 ++ .../webconsole/test/chrome/test_object_actor.html | 158 +++++ .../chrome/test_object_actor_native_getters.html | 75 +++ ...t_object_actor_native_getters_lenient_this.html | 54 ++ .../webconsole/test/chrome/test_page_errors.html | 224 +++++++ .../shared/webconsole/test/xpcshell/.eslintrc.js | 6 + devtools/shared/webconsole/test/xpcshell/head.js | 10 + .../test/xpcshell/test_analyze_input_string.js | 225 +++++++ .../test/xpcshell/test_js_property_provider.js | 746 +++++++++++++++++++++ .../shared/webconsole/test/xpcshell/xpcshell.ini | 9 + 41 files changed, 4992 insertions(+) create mode 100644 devtools/shared/webconsole/test/browser/browser.ini create mode 100644 devtools/shared/webconsole/test/browser/browser_commands_registration.js create mode 100644 devtools/shared/webconsole/test/browser/browser_network_longstring.js create mode 100644 devtools/shared/webconsole/test/browser/data.json create mode 100644 devtools/shared/webconsole/test/browser/data.json^headers^ create mode 100644 devtools/shared/webconsole/test/browser/head.js create mode 100644 devtools/shared/webconsole/test/browser/network_requests_iframe.html create mode 100644 devtools/shared/webconsole/test/chrome/chrome.ini create mode 100644 devtools/shared/webconsole/test/chrome/common.js create mode 100644 devtools/shared/webconsole/test/chrome/console-test-worker.js create mode 100644 devtools/shared/webconsole/test/chrome/data.json create mode 100644 devtools/shared/webconsole/test/chrome/data.json^headers^ create mode 100644 devtools/shared/webconsole/test/chrome/helper_serviceworker.js create mode 100644 devtools/shared/webconsole/test/chrome/network_requests_iframe.html create mode 100644 devtools/shared/webconsole/test/chrome/sandboxed_iframe.html create mode 100644 devtools/shared/webconsole/test/chrome/test_basics.html create mode 100644 devtools/shared/webconsole/test/chrome/test_cached_messages.html create mode 100644 devtools/shared/webconsole/test/chrome/test_console_assert.html create mode 100644 devtools/shared/webconsole/test/chrome/test_console_group_styling.html create mode 100644 devtools/shared/webconsole/test/chrome/test_console_serviceworker.html create mode 100644 devtools/shared/webconsole/test/chrome/test_console_serviceworker_cached.html create mode 100644 devtools/shared/webconsole/test/chrome/test_console_styling.html create mode 100644 devtools/shared/webconsole/test/chrome/test_console_timestamp.html create mode 100644 devtools/shared/webconsole/test/chrome/test_console_worker.html create mode 100644 devtools/shared/webconsole/test/chrome/test_consoleapi.html create mode 100644 devtools/shared/webconsole/test/chrome/test_consoleapi_innerID.html create mode 100644 devtools/shared/webconsole/test/chrome/test_file_uri.html create mode 100644 devtools/shared/webconsole/test/chrome/test_jsterm_autocomplete.html create mode 100644 devtools/shared/webconsole/test/chrome/test_network_get.html create mode 100644 devtools/shared/webconsole/test/chrome/test_network_post.html create mode 100644 devtools/shared/webconsole/test/chrome/test_network_security-hsts.html create mode 100644 devtools/shared/webconsole/test/chrome/test_nsiconsolemessage.html create mode 100644 devtools/shared/webconsole/test/chrome/test_object_actor.html create mode 100644 devtools/shared/webconsole/test/chrome/test_object_actor_native_getters.html create mode 100644 devtools/shared/webconsole/test/chrome/test_object_actor_native_getters_lenient_this.html create mode 100644 devtools/shared/webconsole/test/chrome/test_page_errors.html create mode 100644 devtools/shared/webconsole/test/xpcshell/.eslintrc.js create mode 100644 devtools/shared/webconsole/test/xpcshell/head.js create mode 100644 devtools/shared/webconsole/test/xpcshell/test_analyze_input_string.js create mode 100644 devtools/shared/webconsole/test/xpcshell/test_js_property_provider.js create mode 100644 devtools/shared/webconsole/test/xpcshell/xpcshell.ini (limited to 'devtools/shared/webconsole/test') diff --git a/devtools/shared/webconsole/test/browser/browser.ini b/devtools/shared/webconsole/test/browser/browser.ini new file mode 100644 index 0000000000..1b66699410 --- /dev/null +++ b/devtools/shared/webconsole/test/browser/browser.ini @@ -0,0 +1,13 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + head.js + data.json + data.json^headers^ + network_requests_iframe.html + !/devtools/client/shared/test/shared-head.js + !/devtools/client/shared/test/telemetry-test-helpers.js + +[browser_commands_registration.js] +[browser_network_longstring.js] diff --git a/devtools/shared/webconsole/test/browser/browser_commands_registration.js b/devtools/shared/webconsole/test/browser/browser_commands_registration.js new file mode 100644 index 0000000000..5ab2c848c4 --- /dev/null +++ b/devtools/shared/webconsole/test/browser/browser_commands_registration.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for Web Console commands registration. + +add_task(async function () { + const tab = await addTab("data:text/html,
"); + + const commands = await CommandsFactory.forTab(tab); + await commands.targetCommand.startListening(); + + // Fetch WebConsoleCommandsManager so that it is available for next Content Tasks + await ContentTask.spawn(gBrowser.selectedBrowser, null, function () { + const { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" + ); + const { + WebConsoleCommandsManager, + } = require("resource://devtools/server/actors/webconsole/commands/manager.js"); + + // Bind the symbol on this in order to make it available for next tasks + this.WebConsoleCommandsManager = WebConsoleCommandsManager; + }); + + await registerNewCommand(commands); + await registerAccessor(commands); +}); + +async function evaluateJSAndCheckResult(commands, input, expected) { + const response = await commands.scriptCommand.execute(input); + checkObject(response, expected); +} + +async function registerNewCommand(commands) { + await ContentTask.spawn(gBrowser.selectedBrowser, null, function () { + this.WebConsoleCommandsManager.register("setFoo", (owner, value) => { + owner.window.foo = value; + return "ok"; + }); + }); + + const command = "setFoo('bar')"; + await evaluateJSAndCheckResult(commands, command, { + input: command, + result: "ok", + }); + + await ContentTask.spawn(gBrowser.selectedBrowser, null, function () { + is(content.top.foo, "bar", "top.foo should equal to 'bar'"); + }); +} + +async function registerAccessor(commands) { + await ContentTask.spawn(gBrowser.selectedBrowser, null, function () { + this.WebConsoleCommandsManager.register("$foo", { + get(owner) { + const foo = owner.window.document.getElementById("quack"); + return owner.makeDebuggeeValue(foo); + }, + }); + }); + + const command = "$foo.textContent = '>o_/'"; + await evaluateJSAndCheckResult(commands, command, { + input: command, + result: ">o_/", + }); + + await ContentTask.spawn(gBrowser.selectedBrowser, null, function () { + is( + content.document.getElementById("quack").textContent, + ">o_/", + '#foo textContent should equal to ">o_/"' + ); + }); +} diff --git a/devtools/shared/webconsole/test/browser/browser_network_longstring.js b/devtools/shared/webconsole/test/browser/browser_network_longstring.js new file mode 100644 index 0000000000..782e5c5fc1 --- /dev/null +++ b/devtools/shared/webconsole/test/browser/browser_network_longstring.js @@ -0,0 +1,183 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the network actor uses the LongStringActor + +const { + DevToolsServer, +} = require("resource://devtools/server/devtools-server.js"); +const LONG_STRING_LENGTH = 400; +const LONG_STRING_INITIAL_LENGTH = 400; +let ORIGINAL_LONG_STRING_LENGTH, ORIGINAL_LONG_STRING_INITIAL_LENGTH; + +add_task(async function () { + const tab = await addTab(URL_ROOT + "network_requests_iframe.html"); + + const commands = await CommandsFactory.forTab(tab); + await commands.targetCommand.startListening(); + const target = commands.targetCommand.targetFront; + + // Override the default long string settings to lower values. + // This is done from the parent process's DevToolsServer as the LongString + // actor is being created from the parent process as network requests are + // watched from the parent process. + ORIGINAL_LONG_STRING_LENGTH = DevToolsServer.LONG_STRING_LENGTH; + ORIGINAL_LONG_STRING_INITIAL_LENGTH = + DevToolsServer.LONG_STRING_INITIAL_LENGTH; + + DevToolsServer.LONG_STRING_LENGTH = LONG_STRING_LENGTH; + DevToolsServer.LONG_STRING_INITIAL_LENGTH = LONG_STRING_INITIAL_LENGTH; + + info("test network POST request"); + const networkResource = await new Promise(resolve => { + commands.resourceCommand + .watchResources([commands.resourceCommand.TYPES.NETWORK_EVENT], { + onAvailable: () => {}, + onUpdated: resourceUpdate => { + resolve(resourceUpdate[0].resource); + }, + }) + .then(() => { + // Spawn the network request after we started watching + SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.wrappedJSObject.testXhrPost(); + }); + }); + }); + + const netActor = networkResource.actor; + ok(netActor, "We have a netActor:" + netActor); + + const webConsoleFront = await target.getFront("console"); + const requestHeaders = await webConsoleFront.getRequestHeaders(netActor); + assertRequestHeaders(requestHeaders); + const requestCookies = await webConsoleFront.getRequestCookies(netActor); + assertRequestCookies(requestCookies); + const requestPostData = await webConsoleFront.getRequestPostData(netActor); + assertRequestPostData(requestPostData); + const responseHeaders = await webConsoleFront.getResponseHeaders(netActor); + assertResponseHeaders(responseHeaders); + const responseCookies = await webConsoleFront.getResponseCookies(netActor); + assertResponseCookies(responseCookies); + const responseContent = await webConsoleFront.getResponseContent(netActor); + assertResponseContent(responseContent); + const eventTimings = await webConsoleFront.getEventTimings(netActor); + assertEventTimings(eventTimings); + + await commands.destroy(); + + DevToolsServer.LONG_STRING_LENGTH = ORIGINAL_LONG_STRING_LENGTH; + DevToolsServer.LONG_STRING_INITIAL_LENGTH = + ORIGINAL_LONG_STRING_INITIAL_LENGTH; +}); + +function assertRequestHeaders(response) { + info("checking request headers"); + + ok(!!response.headers.length, "request headers > 0"); + ok(response.headersSize > 0, "request headersSize > 0"); + + checkHeadersOrCookies(response.headers, { + Referer: /network_requests_iframe\.html/, + Cookie: /bug768096/, + }); +} + +function assertRequestCookies(response) { + info("checking request cookies"); + + is(response.cookies.length, 3, "request cookies length"); + + checkHeadersOrCookies(response.cookies, { + foobar: "fooval", + omgfoo: "bug768096", + badcookie: "bug826798=st3fan", + }); +} + +function assertRequestPostData(response) { + info("checking request POST data"); + + checkObject(response, { + postData: { + text: { + type: "longString", + initial: /^Hello world! foobaz barr.+foobaz barrfo$/, + length: 563, + actor: /[a-z]/, + }, + }, + postDataDiscarded: false, + }); + + is( + response.postData.text.initial.length, + LONG_STRING_INITIAL_LENGTH, + "postData text initial length" + ); +} + +function assertResponseHeaders(response) { + info("checking response headers"); + + ok(!!response.headers.length, "response headers > 0"); + ok(response.headersSize > 0, "response headersSize > 0"); + + checkHeadersOrCookies(response.headers, { + "content-type": /^application\/(json|octet-stream)$/, + "content-length": /^\d+$/, + "x-very-short": "hello world", + "x-very-long": { + type: "longString", + length: 521, + initial: /^Lorem ipsum.+\. Donec vitae d$/, + actor: /[a-z]/, + }, + }); +} + +function assertResponseCookies(response) { + info("checking response cookies"); + + is(response.cookies.length, 0, "response cookies length"); +} + +function assertResponseContent(response) { + info("checking response content"); + + checkObject(response, { + content: { + text: { + type: "longString", + initial: /^\{ id: "test JSON data"(.|\r|\n)+ barfoo ba$/g, + length: 1070, + actor: /[a-z]/, + }, + }, + contentDiscarded: false, + }); + + is( + response.content.text.initial.length, + LONG_STRING_INITIAL_LENGTH, + "content initial length" + ); +} + +function assertEventTimings(response) { + info("checking event timings"); + + checkObject(response, { + timings: { + blocked: /^-1|\d+$/, + dns: /^-1|\d+$/, + connect: /^-1|\d+$/, + send: /^-1|\d+$/, + wait: /^-1|\d+$/, + receive: /^-1|\d+$/, + }, + totalTime: /^\d+$/, + }); +} diff --git a/devtools/shared/webconsole/test/browser/data.json b/devtools/shared/webconsole/test/browser/data.json new file mode 100644 index 0000000000..d46085c124 --- /dev/null +++ b/devtools/shared/webconsole/test/browser/data.json @@ -0,0 +1,3 @@ +{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ], + veryLong: "foo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo bar" +} diff --git a/devtools/shared/webconsole/test/browser/data.json^headers^ b/devtools/shared/webconsole/test/browser/data.json^headers^ new file mode 100644 index 0000000000..bb6b45500f --- /dev/null +++ b/devtools/shared/webconsole/test/browser/data.json^headers^ @@ -0,0 +1,3 @@ +Content-Type: application/json +x-very-long: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a ipsum massa. Phasellus at elit dictum libero laoreet sagittis. Phasellus condimentum ultricies imperdiet. Nam eu ligula justo, ut tincidunt quam. Etiam sollicitudin, tortor sed egestas blandit, sapien sem tincidunt nulla, eu luctus libero odio quis leo. Nam elit massa, mattis quis blandit ac, facilisis vitae arcu. Donec vitae dictum neque. Proin ornare nisl at lectus commodo iaculis eget eget est. Quisque scelerisque vestibulum quam sed interdum. +x-very-short: hello world diff --git a/devtools/shared/webconsole/test/browser/head.js b/devtools/shared/webconsole/test/browser/head.js new file mode 100644 index 0000000000..cdfc7ab59a --- /dev/null +++ b/devtools/shared/webconsole/test/browser/head.js @@ -0,0 +1,61 @@ +/* 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"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", + this +); + +function checkObject(object, expected) { + 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 (value === null) { + ok(false, "'" + name + "' is null"); + } else if (value === undefined) { + ok(false, "'" + 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); + } +} + +function checkHeadersOrCookies(array, expected) { + const foundHeaders = {}; + + for (const elem of array) { + if (!(elem.name in expected)) { + continue; + } + foundHeaders[elem.name] = true; + info("checking value of header " + elem.name); + checkValue(elem.name, elem.value, expected[elem.name]); + } + + for (const header in expected) { + if (!(header in foundHeaders)) { + ok(false, header + " was not found"); + } + } +} diff --git a/devtools/shared/webconsole/test/browser/network_requests_iframe.html b/devtools/shared/webconsole/test/browser/network_requests_iframe.html new file mode 100644 index 0000000000..4e96364b06 --- /dev/null +++ b/devtools/shared/webconsole/test/browser/network_requests_iframe.html @@ -0,0 +1,66 @@ + + + + + Console HTTP test page + + + + +

Web Console HTTP Logging Testpage

+

This page is used to test the HTTP logging.

+ +
+
+
+
+ + diff --git a/devtools/shared/webconsole/test/chrome/chrome.ini b/devtools/shared/webconsole/test/chrome/chrome.ini new file mode 100644 index 0000000000..ee903ac727 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/chrome.ini @@ -0,0 +1,34 @@ +[DEFAULT] +tags = devtools +support-files = + common.js + data.json + data.json^headers^ + helper_serviceworker.js + network_requests_iframe.html + sandboxed_iframe.html + console-test-worker.js + !/browser/base/content/test/general/browser_star_hsts.sjs + +[test_basics.html] +[test_cached_messages.html] +[test_consoleapi.html] +[test_consoleapi_innerID.html] +[test_console_assert.html] +[test_console_group_styling.html] +[test_console_serviceworker.html] +[test_console_serviceworker_cached.html] +[test_console_styling.html] +[test_console_timestamp.html] +[test_console_worker.html] +[test_file_uri.html] +[test_jsterm_autocomplete.html] +[test_network_get.html] +skip-if = verify +[test_network_post.html] +[test_network_security-hsts.html] +[test_nsiconsolemessage.html] +[test_object_actor.html] +[test_object_actor_native_getters.html] +[test_object_actor_native_getters_lenient_this.html] +[test_page_errors.html] diff --git a/devtools/shared/webconsole/test/chrome/common.js b/devtools/shared/webconsole/test/chrome/common.js new file mode 100644 index 0000000000..62878d7e60 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/common.js @@ -0,0 +1,274 @@ +/* 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"; + +/* exported attachConsole, attachConsoleToTab, attachConsoleToWorker, + closeDebugger, checkConsoleAPICalls, checkRawHeaders, runTests, nextTest, Ci, Cc, + withActiveServiceWorker, Services, consoleAPICall, createCommandsForTab, FRACTIONAL_NUMBER_REGEX, DevToolsServer */ + +const { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" +); +const { + DevToolsServer, +} = require("resource://devtools/server/devtools-server.js"); +const { + CommandsFactory, +} = require("resource://devtools/shared/commands/commands-factory.js"); + +// timeStamp are the result of a number in microsecond divided by 1000. +// so we can't expect a precise number of decimals, or even if there would +// be decimals at all. +const FRACTIONAL_NUMBER_REGEX = /^\d+(\.\d{1,3})?$/; + +function attachConsole(listeners) { + return _attachConsole(listeners); +} +function attachConsoleToTab(listeners) { + return _attachConsole(listeners, true); +} +function attachConsoleToWorker(listeners) { + return _attachConsole(listeners, true, true); +} + +var _attachConsole = async function (listeners, attachToTab, attachToWorker) { + try { + function waitForMessage(target) { + return new Promise(resolve => { + target.addEventListener("message", resolve, { once: true }); + }); + } + + // Fetch the console actor out of the expected target + // ParentProcessTarget / WorkerTarget / FrameTarget + let commands, target, worker; + if (!attachToTab) { + commands = await CommandsFactory.forMainProcess(); + target = await commands.descriptorFront.getTarget(); + } else { + commands = await CommandsFactory.forCurrentTabInChromeMochitest(); + // Descriptor's getTarget will only work if the TargetCommand watches for the first top target + await commands.targetCommand.startListening(); + target = await commands.descriptorFront.getTarget(); + if (attachToWorker) { + const workerName = "console-test-worker.js#" + new Date().getTime(); + worker = new Worker(workerName); + await waitForMessage(worker); + + const { workers } = await target.listWorkers(); + target = workers.filter(w => w.url == workerName)[0]; + if (!target) { + console.error( + "listWorkers failed. Unable to find the worker actor\n" + ); + return null; + } + // This is still important to attach workers as target is still a descriptor front + // which "becomes" a target when calling this method: + await target.morphWorkerDescriptorIntoWorkerTarget(); + } + } + + // Attach the Target and the target thread in order to instantiate the console client. + await target.attachThread(); + + const webConsoleFront = await target.getFront("console"); + + // By default the console isn't listening for anything, + // request listeners from here + const response = await webConsoleFront.startListeners(listeners); + return { + state: { + dbgClient: commands.client, + webConsoleFront, + actor: webConsoleFront.actor, + // Keep a strong reference to the Worker to avoid it being + // GCd during the test (bug 1237492). + // eslint-disable-next-line camelcase + _worker_ref: worker, + }, + response, + }; + } catch (error) { + console.error( + `attachConsole failed: ${error.error} ${error.message} - ` + error.stack + ); + } + return null; +}; + +async function createCommandsForTab() { + const commands = await CommandsFactory.forMainProcess(); + await commands.targetCommand.startListening(); + return commands; +} + +function closeDebugger(state, callback) { + const onClose = state.dbgClient.close(); + + state.dbgClient = null; + state.client = null; + + if (typeof callback === "function") { + onClose.then(callback); + } + return onClose; +} + +function checkConsoleAPICalls(consoleCalls, expectedConsoleCalls) { + is( + consoleCalls.length, + expectedConsoleCalls.length, + "received correct number of console calls" + ); + expectedConsoleCalls.forEach(function (message, index) { + info("checking received console call #" + index); + checkConsoleAPICall(consoleCalls[index], expectedConsoleCalls[index]); + }); +} + +function checkConsoleAPICall(call, expected) { + is( + call.arguments?.length || 0, + expected.arguments?.length || 0, + "number of arguments" + ); + + checkObject(call, expected); +} + +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) { + ok(!value, "'" + name + "' is null"); + } else if (value === undefined) { + ok(false, "'" + name + "' is undefined"); + } else if (value === null) { + ok(false, "'" + name + "' is null"); + } 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); + } +} + +function checkHeadersOrCookies(array, expected) { + const foundHeaders = {}; + + for (const elem of array) { + if (!(elem.name in expected)) { + continue; + } + foundHeaders[elem.name] = true; + info("checking value of header " + elem.name); + checkValue(elem.name, elem.value, expected[elem.name]); + } + + for (const header in expected) { + if (!(header in foundHeaders)) { + ok(false, header + " was not found"); + } + } +} + +function checkRawHeaders(text, expected) { + const headers = text.split(/\r\n|\n|\r/); + const arr = []; + for (const header of headers) { + const index = header.indexOf(": "); + if (index < 0) { + continue; + } + arr.push({ + name: header.substr(0, index), + value: header.substr(index + 2), + }); + } + + checkHeadersOrCookies(arr, expected); +} + +var gTestState = {}; + +function runTests(tests, endCallback) { + function* driver() { + let lastResult, sendToNext; + for (let i = 0; i < tests.length; i++) { + gTestState.index = i; + const fn = tests[i]; + info("will run test #" + i + ": " + fn.name); + lastResult = fn(sendToNext, lastResult); + sendToNext = yield lastResult; + } + yield endCallback(sendToNext, lastResult); + } + gTestState.driver = driver(); + return gTestState.driver.next(); +} + +function nextTest(message) { + return gTestState.driver.next(message); +} + +function withActiveServiceWorker(win, url, scope) { + const opts = {}; + if (scope) { + opts.scope = scope; + } + return win.navigator.serviceWorker.register(url, opts).then(swr => { + if (swr.active) { + return swr; + } + + // Unfortunately we can't just use navigator.serviceWorker.ready promise + // here. If the service worker is for a scope that does not cover the window + // then the ready promise will never resolve. Instead monitor the service + // workers state change events to determine when its activated. + return new Promise(resolve => { + const sw = swr.waiting || swr.installing; + sw.addEventListener("statechange", function stateHandler(evt) { + if (sw.state === "activated") { + sw.removeEventListener("statechange", stateHandler); + resolve(swr); + } + }); + }); + }); +} + +/** + * + * @param {Front} consoleFront + * @param {Function} consoleCall: A function which calls the consoleAPI, e.g. : + * `() => top.console.log("test")`. + * @returns {Promise} A promise that will be resolved with the packet sent by the server + * in response to the consoleAPI call. + */ +function consoleAPICall(consoleFront, consoleCall) { + const onConsoleAPICall = consoleFront.once("consoleAPICall"); + consoleCall(); + return onConsoleAPICall; +} diff --git a/devtools/shared/webconsole/test/chrome/console-test-worker.js b/devtools/shared/webconsole/test/chrome/console-test-worker.js new file mode 100644 index 0000000000..9e92f6af65 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/console-test-worker.js @@ -0,0 +1,21 @@ +"use strict"; + +console.log("Log from worker init"); + +function f() { + const a = 1; + const b = 2; + const c = 3; + return { a, b, c }; +} + +self.onmessage = function (event) { + if (event.data == "ping") { + f(); + postMessage("pong"); + } else if (event.data?.type == "log") { + console.log(event.data.message); + } +}; + +postMessage("load"); diff --git a/devtools/shared/webconsole/test/chrome/data.json b/devtools/shared/webconsole/test/chrome/data.json new file mode 100644 index 0000000000..eca9d0e796 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/data.json @@ -0,0 +1,5 @@ +{ + "id": "test JSON data", + "myArray": ["foo", "bar", "baz", "biff"], + "veryLong": "foo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo bar" +} diff --git a/devtools/shared/webconsole/test/chrome/data.json^headers^ b/devtools/shared/webconsole/test/chrome/data.json^headers^ new file mode 100644 index 0000000000..bb6b45500f --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/data.json^headers^ @@ -0,0 +1,3 @@ +Content-Type: application/json +x-very-long: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a ipsum massa. Phasellus at elit dictum libero laoreet sagittis. Phasellus condimentum ultricies imperdiet. Nam eu ligula justo, ut tincidunt quam. Etiam sollicitudin, tortor sed egestas blandit, sapien sem tincidunt nulla, eu luctus libero odio quis leo. Nam elit massa, mattis quis blandit ac, facilisis vitae arcu. Donec vitae dictum neque. Proin ornare nisl at lectus commodo iaculis eget eget est. Quisque scelerisque vestibulum quam sed interdum. +x-very-short: hello world diff --git a/devtools/shared/webconsole/test/chrome/helper_serviceworker.js b/devtools/shared/webconsole/test/chrome/helper_serviceworker.js new file mode 100644 index 0000000000..74092183e2 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/helper_serviceworker.js @@ -0,0 +1,21 @@ +"use strict"; + +console.log("script evaluation"); + +addEventListener("install", function (evt) { + console.log("install event"); +}); + +addEventListener("activate", function (evt) { + console.log("activate event"); +}); + +addEventListener("fetch", function (evt) { + console.log("fetch event: " + evt.request.url); + evt.respondWith(new Response("Hello world")); +}); + +addEventListener("message", function (evt) { + console.log("message event: " + evt.data.message); + evt.source.postMessage({ type: "PONG" }); +}); diff --git a/devtools/shared/webconsole/test/chrome/network_requests_iframe.html b/devtools/shared/webconsole/test/chrome/network_requests_iframe.html new file mode 100644 index 0000000000..6bb806b904 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/network_requests_iframe.html @@ -0,0 +1,66 @@ + + + + + Console HTTP test page + + + + +

Web Console HTTP Logging Testpage

+

This page is used to test the HTTP logging.

+ +
+
+
+
+ + diff --git a/devtools/shared/webconsole/test/chrome/sandboxed_iframe.html b/devtools/shared/webconsole/test/chrome/sandboxed_iframe.html new file mode 100644 index 0000000000..55a6224b50 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/sandboxed_iframe.html @@ -0,0 +1,8 @@ + +Sandboxed iframe + + + + diff --git a/devtools/shared/webconsole/test/chrome/test_basics.html b/devtools/shared/webconsole/test/chrome/test_basics.html new file mode 100644 index 0000000000..761a97bc8d --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_basics.html @@ -0,0 +1,61 @@ + + + + + Basic Web Console Actor tests + + + + + +

Basic Web Console Actor tests

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_cached_messages.html b/devtools/shared/webconsole/test/chrome/test_cached_messages.html new file mode 100644 index 0000000000..12d5069c7d --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_cached_messages.html @@ -0,0 +1,217 @@ + + + + + Test for cached messages + + + + + +

Test for cached messages

+ + + + + + diff --git a/devtools/shared/webconsole/test/chrome/test_console_assert.html b/devtools/shared/webconsole/test/chrome/test_console_assert.html new file mode 100644 index 0000000000..f847d5f5d8 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_console_assert.html @@ -0,0 +1,106 @@ + + + + + Test for console.group styling with %c + + + + + + +

+ +
+  
+ + diff --git a/devtools/shared/webconsole/test/chrome/test_console_group_styling.html b/devtools/shared/webconsole/test/chrome/test_console_group_styling.html new file mode 100644 index 0000000000..23a93f8b8d --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_console_group_styling.html @@ -0,0 +1,121 @@ + + + + + Test for console.group styling with %c + + + + + + +

+ +
+  
+ + diff --git a/devtools/shared/webconsole/test/chrome/test_console_serviceworker.html b/devtools/shared/webconsole/test/chrome/test_console_serviceworker.html new file mode 100644 index 0000000000..33b6ba1457 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_console_serviceworker.html @@ -0,0 +1,202 @@ + + + + + Test for the Console API and Service Workers + + + + + +

Test for the Console API and Service Workers

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_console_serviceworker_cached.html b/devtools/shared/webconsole/test/chrome/test_console_serviceworker_cached.html new file mode 100644 index 0000000000..9f94f82f93 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_console_serviceworker_cached.html @@ -0,0 +1,119 @@ + + + + + Test for getCachedMessages and Service Workers + + + + + +

Test for getCachedMessages and Service Workers

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_console_styling.html b/devtools/shared/webconsole/test/chrome/test_console_styling.html new file mode 100644 index 0000000000..841e19076f --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_console_styling.html @@ -0,0 +1,134 @@ + + + + + Test for console.log styling with %c + + + + + +

Test for console.log styling with %c

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_console_timestamp.html b/devtools/shared/webconsole/test/chrome/test_console_timestamp.html new file mode 100644 index 0000000000..a64de0d7b2 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_console_timestamp.html @@ -0,0 +1,48 @@ + + + + + Test for console.group styling with %c + + + + + + +

+ +
+  
+ + diff --git a/devtools/shared/webconsole/test/chrome/test_console_worker.html b/devtools/shared/webconsole/test/chrome/test_console_worker.html new file mode 100644 index 0000000000..330c083f6b --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_console_worker.html @@ -0,0 +1,73 @@ + + + + + Test for the Console API and Workers + + + + + +

Test for the Console API and Workers

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_consoleapi.html b/devtools/shared/webconsole/test/chrome/test_consoleapi.html new file mode 100644 index 0000000000..b5d8edf23e --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_consoleapi.html @@ -0,0 +1,225 @@ + + + + + Test for the Console API + + + + + +

Test for the Console API

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_consoleapi_innerID.html b/devtools/shared/webconsole/test/chrome/test_consoleapi_innerID.html new file mode 100644 index 0000000000..98fc783a84 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_consoleapi_innerID.html @@ -0,0 +1,157 @@ + + + + + Test for the innerID property of the Console API + + + + + +

Test for the Console API

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_file_uri.html b/devtools/shared/webconsole/test/chrome/test_file_uri.html new file mode 100644 index 0000000000..76c3b8193e --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_file_uri.html @@ -0,0 +1,110 @@ + + + + + Test for file activity tracking + + + + + +

Test for file activity tracking

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_jsterm_autocomplete.html b/devtools/shared/webconsole/test/chrome/test_jsterm_autocomplete.html new file mode 100644 index 0000000000..bbc9aeb6fc --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_jsterm_autocomplete.html @@ -0,0 +1,635 @@ + + + + + Test for JavaScript terminal functionality + + + + + +

Test for JavaScript terminal autocomplete functionality

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_network_get.html b/devtools/shared/webconsole/test/chrome/test_network_get.html new file mode 100644 index 0000000000..71bb6c388c --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_network_get.html @@ -0,0 +1,132 @@ + + + + + Test for the network actor (GET request) + + + + + +

Test for the network actor (GET request)

+ + + + + + diff --git a/devtools/shared/webconsole/test/chrome/test_network_post.html b/devtools/shared/webconsole/test/chrome/test_network_post.html new file mode 100644 index 0000000000..237494c323 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_network_post.html @@ -0,0 +1,143 @@ + + + + + Test for the network actor (POST request) + + + + + +

Test for the network actor (POST request)

+ + + + + + diff --git a/devtools/shared/webconsole/test/chrome/test_network_security-hsts.html b/devtools/shared/webconsole/test/chrome/test_network_security-hsts.html new file mode 100644 index 0000000000..6139aa4e05 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_network_security-hsts.html @@ -0,0 +1,89 @@ + + + + + Test for the network actor (HSTS detection) + + + + + +

Test for the network actor (HSTS detection)

+ + + + + + diff --git a/devtools/shared/webconsole/test/chrome/test_nsiconsolemessage.html b/devtools/shared/webconsole/test/chrome/test_nsiconsolemessage.html new file mode 100644 index 0000000000..4e460225df --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_nsiconsolemessage.html @@ -0,0 +1,74 @@ + + + + + Test for nsIConsoleMessages + + + + + +

Make sure that nsIConsoleMessages are logged. See bug 859756.

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_object_actor.html b/devtools/shared/webconsole/test/chrome/test_object_actor.html new file mode 100644 index 0000000000..f42035e2ce --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_object_actor.html @@ -0,0 +1,158 @@ + + + + + Test for the object actor + + + + + +

Test for the object actor

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_object_actor_native_getters.html b/devtools/shared/webconsole/test/chrome/test_object_actor_native_getters.html new file mode 100644 index 0000000000..6ac292fb9a --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_object_actor_native_getters.html @@ -0,0 +1,75 @@ + + + + + Test for the native getters in object actors + + + + + +

Test for the native getters in object actors

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_object_actor_native_getters_lenient_this.html b/devtools/shared/webconsole/test/chrome/test_object_actor_native_getters_lenient_this.html new file mode 100644 index 0000000000..473dfb3b03 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_object_actor_native_getters_lenient_this.html @@ -0,0 +1,54 @@ + + + + + Test that WebIDL attributes with the LenientThis extended attribute + do not appear in the wrong objects + + + + + +

Test for the native getters in object actors

+ + + + diff --git a/devtools/shared/webconsole/test/chrome/test_page_errors.html b/devtools/shared/webconsole/test/chrome/test_page_errors.html new file mode 100644 index 0000000000..0976eed92d --- /dev/null +++ b/devtools/shared/webconsole/test/chrome/test_page_errors.html @@ -0,0 +1,224 @@ + + + + + Test for page errors + + + + + +

Test for page errors

+ + + + diff --git a/devtools/shared/webconsole/test/xpcshell/.eslintrc.js b/devtools/shared/webconsole/test/xpcshell/.eslintrc.js new file mode 100644 index 0000000000..8611c174f5 --- /dev/null +++ b/devtools/shared/webconsole/test/xpcshell/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the common devtools xpcshell eslintrc config. + extends: "../../../../.eslintrc.xpcshell.js", +}; diff --git a/devtools/shared/webconsole/test/xpcshell/head.js b/devtools/shared/webconsole/test/xpcshell/head.js new file mode 100644 index 0000000000..e65552771e --- /dev/null +++ b/devtools/shared/webconsole/test/xpcshell/head.js @@ -0,0 +1,10 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* exported require */ + +"use strict"; + +var { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" +); diff --git a/devtools/shared/webconsole/test/xpcshell/test_analyze_input_string.js b/devtools/shared/webconsole/test/xpcshell/test_analyze_input_string.js new file mode 100644 index 0000000000..3df015056f --- /dev/null +++ b/devtools/shared/webconsole/test/xpcshell/test_analyze_input_string.js @@ -0,0 +1,225 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +"use strict"; +const { + analyzeInputString, +} = require("resource://devtools/shared/webconsole/analyze-input-string.js"); + +add_task(() => { + const tests = [ + { + desc: "simple property access", + input: `var a = {b: 1};a.b`, + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: `var a = {b: 1};a`, + lastStatement: "a.b", + mainExpression: `a`, + matchProp: `b`, + }, + }, + { + desc: "deep property access", + input: `a.b.c`, + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: `a.b`, + lastStatement: "a.b.c", + mainExpression: `a.b`, + matchProp: `c`, + }, + }, + { + desc: "element access", + input: `a["b`, + expected: { + isElementAccess: true, + isPropertyAccess: true, + expressionBeforePropertyAccess: `a`, + lastStatement: `a["b`, + mainExpression: `a`, + matchProp: `"b`, + }, + }, + { + desc: "element access without quotes", + input: `a[b`, + expected: { + isElementAccess: true, + isPropertyAccess: true, + expressionBeforePropertyAccess: `a`, + lastStatement: `a[b`, + mainExpression: `a`, + matchProp: `b`, + }, + }, + { + desc: "simple optional chaining access", + input: `a?.b`, + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: `a`, + lastStatement: `a?.b`, + mainExpression: `a`, + matchProp: `b`, + }, + }, + { + desc: "deep optional chaining access", + input: `a?.b?.c`, + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: `a?.b`, + lastStatement: `a?.b?.c`, + mainExpression: `a?.b`, + matchProp: `c`, + }, + }, + { + desc: "optional chaining element access", + input: `a?.["b`, + expected: { + isElementAccess: true, + isPropertyAccess: true, + expressionBeforePropertyAccess: `a`, + lastStatement: `a?.["b`, + mainExpression: `a`, + matchProp: `"b`, + }, + }, + { + desc: "optional chaining element access without quotes", + input: `a?.[b`, + expected: { + isElementAccess: true, + isPropertyAccess: true, + expressionBeforePropertyAccess: `a`, + lastStatement: `a?.[b`, + mainExpression: `a`, + matchProp: `b`, + }, + }, + { + desc: "deep optional chaining element access with quotes", + input: `var a = {b: 1, c: ["."]}; a?.["b"]?.c?.["d[.`, + expected: { + isElementAccess: true, + isPropertyAccess: true, + expressionBeforePropertyAccess: `var a = {b: 1, c: ["."]}; a?.["b"]?.c`, + lastStatement: `a?.["b"]?.c?.["d[.`, + mainExpression: `a?.["b"]?.c`, + matchProp: `"d[.`, + }, + }, + { + desc: "literal arrays with newline", + input: `[1,2,3,\n4\n].`, + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: `[1,2,3,\n4\n]`, + lastStatement: `[1,2,3,4].`, + mainExpression: `[1,2,3,4]`, + matchProp: ``, + }, + }, + { + desc: "number literal with newline", + input: `1\n.`, + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: `1\n`, + lastStatement: `1\n.`, + mainExpression: `1`, + matchProp: ``, + }, + }, + { + desc: "string literal", + input: `"abc".`, + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: `"abc"`, + lastStatement: `"abc".`, + mainExpression: `"abc"`, + matchProp: ``, + }, + }, + { + desc: "string literal containing backslash", + input: `"\\n".`, + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: `"\\n"`, + lastStatement: `"\\n".`, + mainExpression: `"\\n"`, + matchProp: ``, + }, + }, + { + desc: "single quote string literal containing backslash", + input: `'\\r'.`, + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: `'\\r'`, + lastStatement: `'\\r'.`, + mainExpression: `'\\r'`, + matchProp: ``, + }, + }, + { + desc: "template string literal containing backslash", + input: "`\\\\`.", + expected: { + isElementAccess: false, + isPropertyAccess: true, + expressionBeforePropertyAccess: "`\\\\`", + lastStatement: "`\\\\`.", + mainExpression: "`\\\\`", + matchProp: ``, + }, + }, + { + desc: "unterminated double quote string literal", + input: `"\n`, + expected: { + err: "unterminated string literal", + }, + }, + { + desc: "unterminated single quote string literal", + input: `'\n`, + expected: { + err: "unterminated string literal", + }, + }, + { + desc: "optional chaining operator with spaces", + input: `test ?. ["propA"] ?. [0] ?. ["propB"] ?. ['to`, + expected: { + isElementAccess: true, + isPropertyAccess: true, + expressionBeforePropertyAccess: `test ?. ["propA"] ?. [0] ?. ["propB"] `, + lastStatement: `test ?. ["propA"] ?. [0] ?. ["propB"] ?. ['to`, + mainExpression: `test ?. ["propA"] ?. [0] ?. ["propB"]`, + matchProp: `'to`, + }, + }, + ]; + + for (const { input, desc, expected } of tests) { + const result = analyzeInputString(input); + for (const [key, value] of Object.entries(expected)) { + Assert.equal(value, result[key], `${desc} | ${key} has expected value`); + } + } +}); diff --git a/devtools/shared/webconsole/test/xpcshell/test_js_property_provider.js b/devtools/shared/webconsole/test/xpcshell/test_js_property_provider.js new file mode 100644 index 0000000000..a6f2daee4b --- /dev/null +++ b/devtools/shared/webconsole/test/xpcshell/test_js_property_provider.js @@ -0,0 +1,746 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +"use strict"; +const { + FallibleJSPropertyProvider: JSPropertyProvider, +} = require("resource://devtools/shared/webconsole/js-property-provider.js"); + +const { addDebuggerToGlobal } = ChromeUtils.importESModule( + "resource://gre/modules/jsdebugger.sys.mjs" +); +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"); + + 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? 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); +} diff --git a/devtools/shared/webconsole/test/xpcshell/xpcshell.ini b/devtools/shared/webconsole/test/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..a0d7e75ad1 --- /dev/null +++ b/devtools/shared/webconsole/test/xpcshell/xpcshell.ini @@ -0,0 +1,9 @@ +[DEFAULT] +tags = devtools +head = head.js +firefox-appdir = browser +skip-if = toolkit == 'android' +support-files = + +[test_analyze_input_string.js] +[test_js_property_provider.js] -- cgit v1.2.3