forked from mirrors/gecko-dev
460 lines
13 KiB
JavaScript
460 lines
13 KiB
JavaScript
/* 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);
|
|
}
|
|
|
|
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);
|
|
}
|