diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/shared/commands/resource/tests/browser_resources_console_messages.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/shared/commands/resource/tests/browser_resources_console_messages.js')
-rw-r--r-- | devtools/shared/commands/resource/tests/browser_resources_console_messages.js | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/devtools/shared/commands/resource/tests/browser_resources_console_messages.js b/devtools/shared/commands/resource/tests/browser_resources_console_messages.js new file mode 100644 index 0000000000..6f02cd5a77 --- /dev/null +++ b/devtools/shared/commands/resource/tests/browser_resources_console_messages.js @@ -0,0 +1,623 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the ResourceCommand API around CONSOLE_MESSAGE +// +// Reproduces assertions from: devtools/shared/webconsole/test/chrome/test_cached_messages.html +// And now more. Once we remove the console actor's startListeners in favor of watcher class +// We could remove that other old test. + +const FISSION_TEST_URL = URL_ROOT_SSL + "fission_document.html"; +const IFRAME_URL = URL_ROOT_ORG_SSL + "fission_iframe.html"; + +add_task(async function () { + info("Execute test in top level document"); + await testTabConsoleMessagesResources(false); + await testTabConsoleMessagesResourcesWithIgnoreExistingResources(false); + + info("Execute test in an iframe document, possibly remote with fission"); + await testTabConsoleMessagesResources(true); + await testTabConsoleMessagesResourcesWithIgnoreExistingResources(true); +}); + +async function testTabConsoleMessagesResources(executeInIframe) { + const tab = await addTab(FISSION_TEST_URL); + + const { client, resourceCommand, targetCommand } = await initResourceCommand( + tab + ); + + info( + "Log some messages *before* calling ResourceCommand.watchResources in order to " + + "assert the behavior of already existing messages." + ); + await logExistingMessages(tab.linkedBrowser, executeInIframe); + + const targetDocumentUrl = executeInIframe ? IFRAME_URL : FISSION_TEST_URL; + + let runtimeDoneResolve; + const expectedExistingCalls = + getExpectedExistingConsoleCalls(targetDocumentUrl); + const expectedRuntimeCalls = + getExpectedRuntimeConsoleCalls(targetDocumentUrl); + const onRuntimeDone = new Promise(resolve => (runtimeDoneResolve = resolve)); + const onAvailable = resources => { + for (const resource of resources) { + is( + resource.resourceType, + resourceCommand.TYPES.CONSOLE_MESSAGE, + "Received a message" + ); + ok(resource.message, "message is wrapped into a message attribute"); + const isCachedMessage = !!expectedExistingCalls.length; + const expected = ( + isCachedMessage ? expectedExistingCalls : expectedRuntimeCalls + ).shift(); + checkConsoleAPICall(resource.message, expected); + is( + resource.isAlreadyExistingResource, + isCachedMessage, + "isAlreadyExistingResource has the expected value" + ); + + if (!expectedRuntimeCalls.length) { + runtimeDoneResolve(); + } + } + }; + + await resourceCommand.watchResources( + [resourceCommand.TYPES.CONSOLE_MESSAGE], + { + onAvailable, + } + ); + is( + expectedExistingCalls.length, + 0, + "Got the expected number of existing messages" + ); + + info( + "Now log messages *after* the call to ResourceCommand.watchResources and after having received all existing messages" + ); + await logRuntimeMessages(tab.linkedBrowser, executeInIframe); + + info("Waiting for all runtime messages"); + await onRuntimeDone; + + is( + expectedRuntimeCalls.length, + 0, + "Got the expected number of runtime messages" + ); + + targetCommand.destroy(); + await client.close(); +} + +async function testTabConsoleMessagesResourcesWithIgnoreExistingResources( + executeInIframe +) { + info("Test ignoreExistingResources option for console messages"); + const tab = await addTab(FISSION_TEST_URL); + + const { client, resourceCommand, targetCommand } = await initResourceCommand( + tab + ); + + info( + "Check whether onAvailable will not be called with existing console messages" + ); + await logExistingMessages(tab.linkedBrowser, executeInIframe); + + const availableResources = []; + await resourceCommand.watchResources( + [resourceCommand.TYPES.CONSOLE_MESSAGE], + { + onAvailable: resources => availableResources.push(...resources), + ignoreExistingResources: true, + } + ); + is( + availableResources.length, + 0, + "onAvailable wasn't called for existing console messages" + ); + + info( + "Check whether onAvailable will be called with the future console messages" + ); + await logRuntimeMessages(tab.linkedBrowser, executeInIframe); + const targetDocumentUrl = executeInIframe ? IFRAME_URL : FISSION_TEST_URL; + const expectedRuntimeConsoleCalls = + getExpectedRuntimeConsoleCalls(targetDocumentUrl); + await waitUntil( + () => availableResources.length === expectedRuntimeConsoleCalls.length + ); + const expectedTargetFront = + executeInIframe && (isFissionEnabled() || isEveryFrameTargetEnabled()) + ? targetCommand + .getAllTargets([targetCommand.TYPES.FRAME]) + .find(target => target.url == IFRAME_URL) + : targetCommand.targetFront; + for (let i = 0; i < expectedRuntimeConsoleCalls.length; i++) { + const resource = availableResources[i]; + const { message, targetFront } = resource; + is( + targetFront, + expectedTargetFront, + "The targetFront property is the expected one" + ); + const expected = expectedRuntimeConsoleCalls[i]; + checkConsoleAPICall(message, expected); + is( + resource.isAlreadyExistingResource, + false, + "isAlreadyExistingResource is false since we're ignoring existing resources" + ); + } + + targetCommand.destroy(); + await client.close(); +} + +async function logExistingMessages(browser, executeInIframe) { + let browsingContext = browser.browsingContext; + if (executeInIframe) { + browsingContext = await SpecialPowers.spawn( + browser, + [], + function frameScript() { + return content.document.querySelector("iframe").browsingContext; + } + ); + } + return evalInBrowsingContext(browsingContext, function pageScript() { + console.log("foobarBaz-log", undefined); + console.info("foobarBaz-info", null); + console.warn("foobarBaz-warn", document.body); + }); +} + +/** + * Helper function similar to spawn, but instead of executing the script + * as a Frame Script, with privileges and including test harness in stacktraces, + * execute the script as a regular page script, without privileges and without any + * preceding stack. + * + * @param {BrowsingContext} The browsing context into which the script should be evaluated + * @param {Function|String} The JS to execute in the browsing context + * + * @return {Promise} Which resolves once the JS is done executing in the page + */ +function evalInBrowsingContext(browsingContext, script) { + return SpecialPowers.spawn(browsingContext, [String(script)], expr => { + const document = content.document; + const scriptEl = document.createElement("script"); + document.body.appendChild(scriptEl); + // Force the immediate execution of the stringified JS function passed in `expr` + scriptEl.textContent = "new " + expr; + scriptEl.remove(); + }); +} + +// For both existing and runtime messages, we execute console API +// from a page script evaluated via evalInBrowsingContext. +// Records here the function used to execute the script in the page. +const EXPECTED_FUNCTION_NAME = "pageScript"; + +const NUMBER_REGEX = /^\d+$/; +// 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 getExpectedExistingConsoleCalls(documentFilename) { + const defaultProperties = { + filename: documentFilename, + columnNumber: NUMBER_REGEX, + lineNumber: NUMBER_REGEX, + timeStamp: FRACTIONAL_NUMBER_REGEX, + innerWindowID: NUMBER_REGEX, + chromeContext: undefined, + counter: undefined, + prefix: undefined, + private: undefined, + stacktrace: undefined, + styles: undefined, + timer: undefined, + }; + + return [ + { + ...defaultProperties, + level: "log", + arguments: ["foobarBaz-log", { type: "undefined" }], + }, + { + ...defaultProperties, + level: "info", + arguments: ["foobarBaz-info", { type: "null" }], + }, + { + ...defaultProperties, + level: "warn", + arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }], + }, + ]; +} + +const longString = new Array(DevToolsServer.LONG_STRING_LENGTH + 2).join("a"); +function getExpectedRuntimeConsoleCalls(documentFilename) { + const defaultStackFrames = [ + // This is the usage of "new " + expr from `evalInBrowsingContext` + { + filename: documentFilename, + lineNumber: NUMBER_REGEX, + columnNumber: NUMBER_REGEX, + }, + ]; + + const defaultProperties = { + filename: documentFilename, + columnNumber: NUMBER_REGEX, + lineNumber: NUMBER_REGEX, + timeStamp: FRACTIONAL_NUMBER_REGEX, + innerWindowID: NUMBER_REGEX, + chromeContext: undefined, + counter: undefined, + prefix: undefined, + private: undefined, + stacktrace: undefined, + styles: undefined, + timer: undefined, + }; + + return [ + { + ...defaultProperties, + level: "log", + arguments: ["foobarBaz-log", { type: "undefined" }], + }, + { + ...defaultProperties, + level: "log", + arguments: ["Float from not a number: NaN"], + }, + { + ...defaultProperties, + level: "log", + arguments: ["Float from string: 1.200000"], + }, + { + ...defaultProperties, + level: "log", + arguments: ["Float from number: 1.300000"], + }, + { + ...defaultProperties, + level: "log", + arguments: ["BigInt 123 and 456"], + }, + { + ...defaultProperties, + level: "log", + arguments: ["message with ", "style"], + styles: ["color: blue;", "background: red; font-size: 2em;"], + }, + { + ...defaultProperties, + level: "info", + arguments: ["foobarBaz-info", { type: "null" }], + }, + { + ...defaultProperties, + level: "warn", + arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }], + }, + { + ...defaultProperties, + level: "debug", + arguments: [{ type: "null" }], + }, + { + ...defaultProperties, + level: "trace", + stacktrace: [ + { + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + }, + ...defaultStackFrames, + ], + }, + { + ...defaultProperties, + level: "dir", + arguments: [ + { + type: "object", + actor: /[a-z]/, + class: "HTMLDocument", + }, + { + type: "object", + actor: /[a-z]/, + class: "Location", + }, + ], + }, + { + ...defaultProperties, + level: "log", + arguments: [ + "foo", + { + type: "longString", + initial: longString.substring( + 0, + DevToolsServer.LONG_STRING_INITIAL_LENGTH + ), + length: longString.length, + actor: /[a-z]/, + }, + ], + }, + { + ...defaultProperties, + level: "count", + arguments: ["myCounter"], + counter: { + count: 1, + label: "myCounter", + }, + }, + { + ...defaultProperties, + level: "count", + arguments: ["myCounter"], + counter: { + count: 2, + label: "myCounter", + }, + }, + { + ...defaultProperties, + level: "count", + arguments: ["default"], + counter: { + count: 1, + label: "default", + }, + }, + { + ...defaultProperties, + level: "countReset", + arguments: ["myCounter"], + counter: { + count: 0, + label: "myCounter", + }, + }, + { + ...defaultProperties, + level: "countReset", + arguments: ["unknownCounter"], + counter: { + error: "counterDoesntExist", + label: "unknownCounter", + }, + }, + { + ...defaultProperties, + level: "time", + arguments: ["myTimer"], + timer: { + name: "myTimer", + }, + }, + { + ...defaultProperties, + level: "time", + arguments: ["myTimer"], + timer: { + name: "myTimer", + error: "timerAlreadyExists", + }, + }, + { + ...defaultProperties, + level: "timeLog", + arguments: ["myTimer"], + timer: { + name: "myTimer", + duration: NUMBER_REGEX, + }, + }, + { + ...defaultProperties, + level: "timeEnd", + arguments: ["myTimer"], + timer: { + name: "myTimer", + duration: NUMBER_REGEX, + }, + }, + { + ...defaultProperties, + level: "time", + arguments: ["default"], + timer: { + name: "default", + }, + }, + { + ...defaultProperties, + level: "timeLog", + arguments: ["default"], + timer: { + name: "default", + duration: NUMBER_REGEX, + }, + }, + { + ...defaultProperties, + level: "timeEnd", + arguments: ["default"], + timer: { + name: "default", + duration: NUMBER_REGEX, + }, + }, + { + ...defaultProperties, + level: "timeLog", + arguments: ["unknownTimer"], + timer: { + name: "unknownTimer", + error: "timerDoesntExist", + }, + }, + { + ...defaultProperties, + level: "timeEnd", + arguments: ["unknownTimer"], + timer: { + name: "unknownTimer", + error: "timerDoesntExist", + }, + }, + { + ...defaultProperties, + level: "error", + arguments: ["foobarBaz-asmjs-error", { type: "undefined" }], + + stacktrace: [ + { + filename: documentFilename, + functionName: "fromAsmJS", + }, + { + filename: documentFilename, + functionName: "inAsmJS2", + }, + { + filename: documentFilename, + functionName: "inAsmJS1", + }, + { + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + }, + ...defaultStackFrames, + ], + }, + { + ...defaultProperties, + level: "log", + filename: + "chrome://mochitests/content/browser/devtools/shared/commands/resource/tests/browser_resources_console_messages.js", + arguments: [ + { + type: "object", + actor: /[a-z]/, + class: "Restricted", + }, + ], + chromeContext: true, + }, + ]; +} + +async function logRuntimeMessages(browser, executeInIframe) { + let browsingContext = browser.browsingContext; + if (executeInIframe) { + browsingContext = await SpecialPowers.spawn( + browser, + [], + function frameScript() { + return content.document.querySelector("iframe").browsingContext; + } + ); + } + // First inject LONG_STRING_LENGTH in global scope it order to easily use it after + await evalInBrowsingContext( + browsingContext, + `function () {window.LONG_STRING_LENGTH = ${DevToolsServer.LONG_STRING_LENGTH};}` + ); + await evalInBrowsingContext(browsingContext, function pageScript() { + const _longString = new Array(window.LONG_STRING_LENGTH + 2).join("a"); + + console.log("foobarBaz-log", undefined); + + console.log("Float from not a number: %f", "foo"); + console.log("Float from string: %f", "1.2"); + console.log("Float from number: %f", 1.3); + console.log("BigInt %d and %i", 123n, 456n); + console.log( + "%cmessage with %cstyle", + "color: blue;", + "background: red; font-size: 2em;" + ); + + console.info("foobarBaz-info", null); + console.warn("foobarBaz-warn", document.documentElement); + console.debug(null); + console.trace(); + console.dir(document, location); + console.log("foo", _longString); + + console.count("myCounter"); + console.count("myCounter"); + console.count(); + console.countReset("myCounter"); + // will cause warnings because unknownCounter doesn't exist + console.countReset("unknownCounter"); + + console.time("myTimer"); + // will cause warning because myTimer already exist + console.time("myTimer"); + console.timeLog("myTimer"); + console.timeEnd("myTimer"); + console.time(); + console.timeLog(); + console.timeEnd(); + // // will cause warnings because unknownTimer doesn't exist + console.timeLog("unknownTimer"); + console.timeEnd("unknownTimer"); + + function fromAsmJS() { + console.error("foobarBaz-asmjs-error", undefined); + } + + (function (global, foreign) { + "use asm"; + function inAsmJS2() { + foreign.fromAsmJS(); + } + function inAsmJS1() { + inAsmJS2(); + } + return inAsmJS1; + })(null, { fromAsmJS })(); + }); + await SpecialPowers.spawn(browsingContext, [], function frameScript() { + const sandbox = new Cu.Sandbox(null, { invisibleToDebugger: true }); + const sandboxObj = sandbox.eval("new Object"); + content.console.log(sandboxObj); + }); +} + +// Copied from devtools/shared/webconsole/test/chrome/common.js +function checkConsoleAPICall(call, expected) { + is( + call.arguments?.length || 0, + expected.arguments?.length || 0, + "number of arguments" + ); + + checkObject(call, expected); +} |