diff options
Diffstat (limited to 'devtools/shared/resources/tests/browser_resources_console_messages.js')
-rw-r--r-- | devtools/shared/resources/tests/browser_resources_console_messages.js | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/devtools/shared/resources/tests/browser_resources_console_messages.js b/devtools/shared/resources/tests/browser_resources_console_messages.js new file mode 100644 index 0000000000..1be72427b1 --- /dev/null +++ b/devtools/shared/resources/tests/browser_resources_console_messages.js @@ -0,0 +1,460 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the ResourceWatcher 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 { + ResourceWatcher, +} = require("devtools/shared/resources/resource-watcher"); + +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, resourceWatcher, targetList } = await initResourceWatcher( + tab + ); + + info( + "Log some messages *before* calling ResourceWatcher.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) { + if (resource.message.arguments?.[0] === "[WORKER] started") { + // XXX Ignore message from workers as we can't know when they're logged, and we + // have a dedicated test for them (browser_resources_console_messages_workers.js). + continue; + } + + is( + resource.resourceType, + ResourceWatcher.TYPES.CONSOLE_MESSAGE, + "Received a message" + ); + ok(resource.message, "message is wrapped into a message attribute"); + const expected = (expectedExistingCalls.length > 0 + ? expectedExistingCalls + : expectedRuntimeCalls + ).shift(); + checkConsoleAPICall(resource.message, expected); + if (expectedRuntimeCalls.length == 0) { + runtimeDoneResolve(); + } + } + }; + + await resourceWatcher.watchResources( + [ResourceWatcher.TYPES.CONSOLE_MESSAGE], + { + onAvailable, + } + ); + is( + expectedExistingCalls.length, + 0, + "Got the expected number of existing messages" + ); + + info( + "Now log messages *after* the call to ResourceWatcher.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" + ); + + targetList.destroy(); + await client.close(); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + // registrationPromise is set by the test page. + const registration = await content.wrappedJSObject.registrationPromise; + registration.unregister(); + }); +} + +async function testTabConsoleMessagesResourcesWithIgnoreExistingResources( + executeInIframe +) { + info("Test ignoreExistingResources option for console messages"); + const tab = await addTab(FISSION_TEST_URL); + + const { client, resourceWatcher, targetList } = await initResourceWatcher( + tab + ); + + info( + "Check whether onAvailable will not be called with existing console messages" + ); + await logExistingMessages(tab.linkedBrowser, executeInIframe); + + const availableResources = []; + await resourceWatcher.watchResources( + [ResourceWatcher.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() + ? targetList + .getAllTargets([targetList.TYPES.FRAME]) + .find(target => target.url == IFRAME_URL) + : targetList.targetFront; + for (let i = 0; i < expectedRuntimeConsoleCalls.length; i++) { + const { message, targetFront } = availableResources[i]; + is( + targetFront, + expectedTargetFront, + "The targetFront property is the expected one" + ); + const expected = expectedRuntimeConsoleCalls[i]; + checkConsoleAPICall(message, expected); + } + + await targetList.destroy(); + await client.close(); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + // registrationPromise is set by the test page. + const registration = await content.wrappedJSObject.registrationPromise; + registration.unregister(); + }); +} + +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+$/; + +function getExpectedExistingConsoleCalls(documentFilename) { + return [ + { + level: "log", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + arguments: ["foobarBaz-log", { type: "undefined" }], + }, + { + level: "info", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + arguments: ["foobarBaz-info", { type: "null" }], + }, + { + level: "warn", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + 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: 1, + columnNumber: NUMBER_REGEX, + }, + ]; + + return [ + { + level: "log", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + arguments: ["foobarBaz-log", { type: "undefined" }], + }, + { + level: "log", + arguments: ["Float from not a number: NaN"], + }, + { + level: "log", + arguments: ["Float from string: 1.200000"], + }, + { + level: "log", + arguments: ["Float from number: 1.300000"], + }, + { + level: "info", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + arguments: ["foobarBaz-info", { type: "null" }], + }, + { + level: "warn", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }], + }, + { + level: "debug", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + arguments: [{ type: "null" }], + }, + { + level: "trace", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + stacktrace: [ + { + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + }, + ...defaultStackFrames, + ], + }, + { + level: "dir", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + arguments: [ + { + type: "object", + actor: /[a-z]/, + class: "HTMLDocument", + }, + { + type: "object", + actor: /[a-z]/, + class: "Location", + }, + ], + }, + { + level: "log", + filename: documentFilename, + functionName: EXPECTED_FUNCTION_NAME, + timeStamp: NUMBER_REGEX, + arguments: [ + "foo", + { + type: "longString", + initial: longString.substring( + 0, + DevToolsServer.LONG_STRING_INITIAL_LENGTH + ), + length: longString.length, + actor: /[a-z]/, + }, + ], + }, + { + level: "error", + filename: documentFilename, + functionName: "fromAsmJS", + timeStamp: NUMBER_REGEX, + 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, + ], + }, + { + level: "log", + filename: gTestPath, + functionName: "frameScript", + timeStamp: NUMBER_REGEX, + arguments: [ + { + type: "object", + actor: /[a-z]/, + class: "Restricted", + }, + ], + }, + ]; +} + +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.info("foobarBaz-info", null); + console.warn("foobarBaz-warn", document.documentElement); + console.debug(null); + console.trace(); + console.dir(document, location); + console.log("foo", _longString); + + function fromAsmJS() { + console.error("foobarBaz-asmjs-error", undefined); + } + + (function(global, foreign) { + "use asm"; + function inAsmJS2() { + foreign.fromAsmJS(); + } + function inAsmJS1() { + inAsmJS2(); + } + return inAsmJS1; + })(null, { fromAsmJS: 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); +} |