Bug 1644186 - Add server support for Error messages resources. r=ochameau.

Differential Revision: https://phabricator.services.mozilla.com/D81356
This commit is contained in:
Nicolas Chevobbe 2020-07-09 12:39:22 +00:00
Родитель c69f56d7dd
Коммит d7fdb5bb73
14 изменённых файлов: 713 добавлений и 70 удалений

Просмотреть файл

@ -36,7 +36,10 @@ add_task(async function() {
});
async function waitForMessageAndViewSource(hud, message) {
const msg = await waitFor(() => findMessage(hud, message));
const msg = await waitFor(
() => findMessage(hud, message),
`Message "${message}" wasn't found`
);
ok(msg, `Message found: "${message}"`);
const locationNode = msg.querySelector(

Просмотреть файл

@ -82,7 +82,6 @@ rawPackets.set(`ReferenceError: asdf is not defined`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": {
@ -164,7 +163,6 @@ rawPackets.set(`SyntaxError: redeclaration of let a`, {
}
],
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": {
@ -250,7 +248,6 @@ rawPackets.set(`TypeError longString message`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": {
@ -336,7 +333,6 @@ rawPackets.set(`throw string with URL`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": "“https://evil.com/?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa“ is evil and “https://not-so-evil.com/?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa“ is not good either",
@ -394,7 +390,6 @@ rawPackets.set(`throw ""`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": "",
@ -452,7 +447,6 @@ rawPackets.set(`throw "tomato"`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": "tomato",
@ -510,7 +504,6 @@ rawPackets.set(`throw false`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": false,
@ -568,7 +561,6 @@ rawPackets.set(`throw 0`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": 0,
@ -626,7 +618,6 @@ rawPackets.set(`throw null`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": {
@ -686,7 +677,6 @@ rawPackets.set(`throw undefined`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": {
@ -746,7 +736,6 @@ rawPackets.set(`throw Symbol`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": {
@ -808,7 +797,6 @@ rawPackets.set(`throw Object`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": {
@ -892,7 +880,6 @@ rawPackets.set(`throw Error Object`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": {
@ -970,7 +957,6 @@ rawPackets.set(`throw Error Object with custom name`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": false,
"isForwardedFromContentProcess": false,
"exception": {
@ -1048,7 +1034,6 @@ rawPackets.set(`Promise reject ""`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": "",
@ -1106,7 +1091,6 @@ rawPackets.set(`Promise reject "tomato"`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": "tomato",
@ -1164,7 +1148,6 @@ rawPackets.set(`Promise reject false`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": false,
@ -1222,7 +1205,6 @@ rawPackets.set(`Promise reject 0`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": 0,
@ -1280,7 +1262,6 @@ rawPackets.set(`Promise reject null`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": {
@ -1340,7 +1321,6 @@ rawPackets.set(`Promise reject undefined`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": {
@ -1400,7 +1380,6 @@ rawPackets.set(`Promise reject Symbol`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": {
@ -1462,7 +1441,6 @@ rawPackets.set(`Promise reject Object`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": {
@ -1546,7 +1524,6 @@ rawPackets.set(`Promise reject Error Object`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": {
@ -1624,7 +1601,6 @@ rawPackets.set(`Promise reject Error Object with custom name`, {
],
"notes": null,
"chromeContext": false,
"cssSelectors": "",
"isPromiseRejection": true,
"isForwardedFromContentProcess": false,
"exception": {

Просмотреть файл

@ -88,13 +88,16 @@ exports.WatcherActor = protocol.ActorClassWithSpec(watcherSpec, {
false
);
const hasBrowserElement = !!this.browserElement;
return {
actor: this.actorID,
traits: {
// FF77+ supports frames in Watcher actor
frame: true,
resources: {
// FF79+ supports console messages and platform messages, but this isn't enabled yet.
// FF79+ supports console and platform messages, FF80+ supports error messages,
// but this isn't enabled yet.
// We will implement a few other resources before enabling it in bug 1642295.
// This is to prevent having to handle backward compat if we have to change Watcher
// Actor API while implementing other resources.
@ -102,7 +105,9 @@ exports.WatcherActor = protocol.ActorClassWithSpec(watcherSpec, {
// content process targets yet. Bug 1620248 should help supporting them and enable
// this more broadly.
[Resources.TYPES.CONSOLE_MESSAGE]:
enableServerWatcher && !!this.browserElement,
enableServerWatcher && hasBrowserElement,
[Resources.TYPES.ERROR_MESSAGE]:
enableServerWatcher && hasBrowserElement,
[Resources.TYPES.PLATFORM_MESSAGE]: enableServerWatcher,
},
},

Просмотреть файл

@ -0,0 +1,275 @@
/* 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";
const { Ci, Cu } = require("chrome");
const Services = require("Services");
const ChromeUtils = require("ChromeUtils");
const { DevToolsServer } = require("devtools/server/devtools-server");
const ErrorDocs = require("devtools/server/actors/errordocs");
const {
createStringGrip,
makeDebuggeeValue,
createValueGripForTarget,
} = require("devtools/server/actors/object/utils");
const {
getActorIdForInternalSourceId,
} = require("devtools/server/actors/utils/dbg-source");
const {
TYPES: { ERROR_MESSAGE },
} = require("devtools/server/actors/resources/index");
class ErrorMessageWatcher {
/**
* Start watching for all error messages related to a given Target Actor.
* This will notify about existing console messages, but also the one created in future.
*
* @param TargetActor targetActor
* The target actor from which we should observe console messages
* @param Object options
* Dictionary object with following attributes:
* - onAvailable: mandatory function
* This will be called for each resource.
*/
constructor(targetActor, { onAvailable }) {
// The following code expects the ThreadActor to be instantiated, via:
// buildPageErrorResource > TabSources.getActorIdForInternalSourceId
// The Thread Actor is instantiated via Target.attach, but we should
// probably review this and only instantiate the actor instead of attaching the target.
targetActor.attach();
// Create the consoleListener.
const listener = {
QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
observe(message) {
if (!shouldHandleMessage(targetActor, message)) {
return;
}
onAvailable([buildPageErrorResource(targetActor, message)]);
},
};
// Retrieve the cached messages just before registering the listener, so we don't get
// duplicated messages.
const cachedMessages = Services.console.getMessageArray() || [];
Services.console.registerListener(listener);
this.listener = listener;
// Remove unwanted cache messages and send an array of resources.
const messages = [];
for (const message of cachedMessages) {
if (!shouldHandleMessage(targetActor, message)) {
continue;
}
messages.push(buildPageErrorResource(targetActor, message));
}
onAvailable(messages);
}
/**
* Stop watching for console messages.
*/
destroy() {
if (this.listener) {
Services.console.unregisterListener(this.listener);
}
}
}
module.exports = ErrorMessageWatcher;
/**
* Returns true if the message is considered an error message, and as a result, should
* be sent to the client.
*
* @param {nsIConsoleMessage|nsIScriptError} message
*/
function shouldHandleMessage(targetActor, message) {
// The listener we use can be called either with a nsIConsoleMessage or a nsIScriptError.
// In this file, we only want to handle nsIScriptError.
if (
// We only care about nsIScriptError
!(message instanceof Ci.nsIScriptError) ||
!isCategoryAllowed(targetActor, message.category) ||
// Block any error that was triggered by eager evaluation
message.sourceName === "debugger eager eval code" ||
// In Process targets don't include messages from private window
(isProcessTarget(targetActor) && message.isFromPrivateWindow)
) {
return false;
}
// Process targets listen for everything
if (isProcessTarget(targetActor)) {
return true;
}
if (!message.innerWindowID) {
return false;
}
const { window } = targetActor.window;
const win = window?.WindowGlobalChild?.getByInnerWindowId(
message.innerWindowID
);
return targetActor.browserId === win?.browsingContext?.browserId;
}
const PLATFORM_SPECIFIC_CATEGORIES = [
"XPConnect JavaScript",
"component javascript",
"chrome javascript",
"chrome registration",
];
/**
* Check if the given message category is allowed to be tracked or not.
* We ignore chrome-originating errors as we only care about content.
*
* @param string category
* The message category you want to check.
* @return boolean
* True if the category is allowed to be logged, false otherwise.
*/
function isCategoryAllowed(targetActor, category) {
// CSS Parser errors will be handled by the CSSMessageWatcher.
if (!category || category === "CSS Parser") {
return false;
}
// We listen for everything on Process targets
if (isProcessTarget(targetActor)) {
return true;
}
// For non-process targets, we filter-out platform-specific errors.
return !PLATFORM_SPECIFIC_CATEGORIES.includes(category);
}
function isProcessTarget(targetActor) {
const { typeName } = targetActor;
return (
typeName === "parentProcessTarget" || typeName === "contentProcessTarget"
);
}
/**
* Prepare an nsIScriptError to be sent to the client.
*
* @param nsIScriptError error
* The page error we need to send to the client.
* @return object
* The object you can send to the remote client.
*/
function buildPageErrorResource(targetActor, error) {
const stack = prepareStackForRemote(targetActor, error.stack);
let lineText = error.sourceLine;
if (lineText && lineText.length > DevToolsServer.LONG_STRING_INITIAL_LENGTH) {
lineText = lineText.substr(0, DevToolsServer.LONG_STRING_INITIAL_LENGTH);
}
let notesArray = null;
const notes = error.notes;
if (notes?.length) {
notesArray = [];
for (let i = 0, len = notes.length; i < len; i++) {
const note = notes.queryElementAt(i, Ci.nsIScriptErrorNote);
notesArray.push({
messageBody: createStringGrip(targetActor, note.errorMessage),
frame: {
source: note.sourceName,
sourceId: getActorIdForInternalSourceId(targetActor, note.sourceId),
line: note.lineNumber,
column: note.columnNumber,
},
});
}
}
// If there is no location information in the error but we have a stack,
// fill in the location with the first frame on the stack.
let { sourceName, sourceId, lineNumber, columnNumber } = error;
if (!sourceName && !sourceId && !lineNumber && !columnNumber && stack) {
sourceName = stack[0].filename;
sourceId = stack[0].sourceId;
lineNumber = stack[0].lineNumber;
columnNumber = stack[0].columnNumber;
}
const pageError = {
errorMessage: createStringGrip(targetActor, error.errorMessage),
errorMessageName: error.errorMessageName,
exceptionDocURL: ErrorDocs.GetURL(error),
sourceName,
sourceId: getActorIdForInternalSourceId(targetActor, sourceId),
lineText,
lineNumber,
columnNumber,
category: error.category,
innerWindowID: error.innerWindowID,
timeStamp: error.timeStamp,
warning: !!(error.flags & error.warningFlag),
error: !(error.flags & (error.warningFlag | error.infoFlag)),
info: !!(error.flags & error.infoFlag),
private: error.isFromPrivateWindow,
stacktrace: stack,
notes: notesArray,
chromeContext: error.isFromChromeContext,
isPromiseRejection: error.isPromiseRejection,
isForwardedFromContentProcess: error.isForwardedFromContentProcess,
};
// If the pageError does have an exception object, we want to return the grip for it,
// but only if we do manage to get the grip, as we're checking the property on the
// client to render things differently.
if (error.hasException) {
try {
const obj = makeDebuggeeValue(targetActor, error.exception);
if (obj?.class !== "DeadObject") {
pageError.exception = createValueGripForTarget(targetActor, obj);
pageError.hasException = true;
}
} catch (e) {}
}
return {
pageError,
resourceType: ERROR_MESSAGE,
};
}
/**
* Prepare a SavedFrame stack to be sent to the client.
*
* @param SavedFrame errorStack
* Stack for an error we need to send to the client.
* @return object
* The object you can send to the remote client.
*/
function prepareStackForRemote(targetActor, errorStack) {
// Convert stack objects to the JSON attributes expected by client code
// Bug 1348885: If the global from which this error came from has been
// nuked, stack is going to be a dead wrapper.
if (!errorStack || (Cu && Cu.isDeadWrapper(errorStack))) {
return null;
}
const stack = [];
let s = errorStack;
while (s) {
stack.push({
filename: s.source,
sourceId: getActorIdForInternalSourceId(targetActor, s.sourceId),
lineNumber: s.line,
columnNumber: s.column,
functionName: s.functionDisplayName,
asyncCause: s.asyncCause ? s.asyncCause : undefined,
});
s = s.parent || s.asyncParent;
}
return stack;
}

Просмотреть файл

@ -6,6 +6,7 @@
const TYPES = {
CONSOLE_MESSAGE: "console-message",
ERROR_MESSAGE: "error-message",
PLATFORM_MESSAGE: "platform-message",
};
exports.TYPES = TYPES;
@ -24,6 +25,9 @@ const Resources = {
[TYPES.CONSOLE_MESSAGE]: {
path: "devtools/server/actors/resources/console-messages",
},
[TYPES.ERROR_MESSAGE]: {
path: "devtools/server/actors/resources/error-messages",
},
[TYPES.PLATFORM_MESSAGE]: {
path: "devtools/server/actors/resources/platform-messages",
},

Просмотреть файл

@ -6,6 +6,7 @@
DevToolsModules(
'console-messages.js',
'error-messages.js',
'index.js',
'platform-messages.js',
)

Просмотреть файл

@ -17,7 +17,7 @@ const { LongStringActor } = require("devtools/server/actors/string");
class PlatformMessageWatcher {
/**
* Start watching for all console messages related to a given Target Actor.
* Start watching for all platform messages related to a given Target Actor.
* This will notify about existing console messages, but also the one created in future.
*
* @param TargetActor targetActor

Просмотреть файл

@ -1726,11 +1726,14 @@ const WebConsoleActor = ActorClassWithSpec(webconsoleSpec, {
stacktrace: stack,
notes: notesArray,
chromeContext: pageError.isFromChromeContext,
cssSelectors: pageError.cssSelectors,
isPromiseRejection: pageError.isPromiseRejection,
isForwardedFromContentProcess: pageError.isForwardedFromContentProcess,
};
if (pageError.category === "CSS Parser") {
result.cssSelectors = pageError.cssSelectors;
}
// If the pageError does have an exception object, we want to return the grip for it,
// but only if we do manage to get the grip, as we're checking the property on the
// client to render things differently.

Просмотреть файл

@ -503,4 +503,6 @@ const LegacyListeners = {
const ResourceTransformers = {
[ResourceWatcher.TYPES
.CONSOLE_MESSAGE]: require("devtools/shared/resources/transformers/console-messages"),
[ResourceWatcher.TYPES
.ERROR_MESSAGE]: require("devtools/shared/resources/transformers/error-messages"),
};

Просмотреть файл

@ -154,26 +154,43 @@ function logExistingMessages(browser) {
content.console.warn("foobarBaz-warn", content.document.body);
});
}
const NUMBER_REGEX = /^\d+$/;
const defaultStackFrames = [
{
filename: "resource://testing-common/content-task.js",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
},
{
filename: "resource://testing-common/content-task.js",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
asyncCause: "MessageListener.receiveMessage",
},
];
const expectedExistingConsoleCalls = [
{
level: "log",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: ["foobarBaz-log", { type: "undefined" }],
},
{
level: "info",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: ["foobarBaz-info", { type: "null" }],
},
{
level: "warn",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
},
];
@ -184,7 +201,7 @@ const expectedRuntimeConsoleCalls = [
level: "log",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: ["foobarBaz-log", { type: "undefined" }],
},
{
@ -203,40 +220,41 @@ const expectedRuntimeConsoleCalls = [
level: "info",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: ["foobarBaz-info", { type: "null" }],
},
{
level: "warn",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
},
{
level: "debug",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: [{ type: "null" }],
},
{
level: "trace",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
stacktrace: [
{
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
},
...defaultStackFrames,
],
},
{
level: "dir",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: [
{
type: "object",
@ -254,7 +272,7 @@ const expectedRuntimeConsoleCalls = [
level: "log",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: [
"foo",
{
@ -272,7 +290,7 @@ const expectedRuntimeConsoleCalls = [
level: "log",
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: [
{
type: "object",
@ -285,7 +303,7 @@ const expectedRuntimeConsoleCalls = [
level: "error",
filename: EXPECTED_FILENAME,
functionName: "fromAsmJS",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
arguments: ["foobarBaz-asmjs-error", { type: "undefined" }],
stacktrace: [
@ -305,6 +323,7 @@ const expectedRuntimeConsoleCalls = [
filename: EXPECTED_FILENAME,
functionName: EXPECTED_FUNCTION_NAME,
},
...defaultStackFrames,
],
},
];

Просмотреть файл

@ -24,6 +24,18 @@ add_task(async function() {
// which forces the emission of RDP requests we aren't correctly waiting for.
await pushPref("dom.ipc.processPrelaunch.enabled", false);
info("Test error messages legacy listener");
await testErrorMessagesResources();
await testErrorMessagesResourcesWithIgnoreExistingResources();
info("Test error messages server listener");
await pushPref("devtools.testing.enableServerWatcherSupport", true);
await testErrorMessagesResources();
await testErrorMessagesResourcesWithIgnoreExistingResources();
await pushPref("devtools.testing.enableServerWatcherSupport", false);
});
async function testErrorMessagesResources() {
// Open a test tab
const tab = await addTab(TEST_URI);
@ -93,15 +105,10 @@ add_task(async function() {
Services.console.reset();
targetList.stopListening();
await client.close();
});
}
add_task(async function() {
async function testErrorMessagesResourcesWithIgnoreExistingResources() {
info("Test ignoreExistingResources option for ERROR_MESSAGE");
// Disable the preloaded process as it creates processes intermittently
// which forces the emission of RDP requests we aren't correctly waiting for.
await pushPref("dom.ipc.processPrelaunch.enabled", false);
const tab = await addTab(TEST_URI);
const {
@ -142,7 +149,7 @@ add_task(async function() {
Services.console.reset();
await targetList.stopListening();
await client.close();
});
}
/**
* Triggers all the errors in the content page.
@ -174,6 +181,31 @@ async function triggerErrors(tab) {
}
const noUncaughtException = Symbol();
const NUMBER_REGEX = /^\d+$/;
const mdnUrl = path =>
`https://developer.mozilla.org/${path}?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default`;
const defaultStackFrames = [
{
filename: /resource:\/\/testing-common\/content-task.js/,
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
functionName: "frameScript",
},
{
filename: "resource://testing-common/content-task.js",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
functionName: null,
},
{
filename: "resource://testing-common/content-task.js",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
functionName: null,
asyncCause: "MessageListener.receiveMessage",
},
];
const expectedPageErrors = new Map([
[
@ -183,9 +215,33 @@ const expectedPageErrors = new Map([
errorMessageName: undefined,
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl(
"docs/Web/JavaScript/Reference/Errors/Not_a_function"
),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 1,
columnNumber: 10,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
@ -195,9 +251,31 @@ const expectedPageErrors = new Map([
errorMessageName: "JSMSG_BAD_RADIX",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl("docs/Web/JavaScript/Reference/Errors/Bad_radix"),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 1,
columnNumber: 6,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
@ -207,9 +285,31 @@ const expectedPageErrors = new Map([
errorMessageName: "JSMSG_READ_ONLY",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl("docs/Web/JavaScript/Reference/Errors/Read-only"),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 1,
columnNumber: 23,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
@ -219,9 +319,33 @@ const expectedPageErrors = new Map([
errorMessageName: "JSMSG_BAD_ARRAY_LENGTH",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl(
"docs/Web/JavaScript/Reference/Errors/Invalid_array_length"
),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 1,
columnNumber: 2,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
@ -231,9 +355,40 @@ const expectedPageErrors = new Map([
errorMessageName: "JSMSG_NEGATIVE_REPETITION_COUNT",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl(
"docs/Web/JavaScript/Reference/Errors/Negative_repetition_count"
),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: "self-hosted",
sourceId: null,
lineNumber: NUMBER_REGEX,
columnNumber: 1,
functionName: "repeat",
},
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 1,
columnNumber: 7,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
@ -243,9 +398,40 @@ const expectedPageErrors = new Map([
errorMessageName: "JSMSG_RESULTING_STRING_TOO_LARGE",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl(
"docs/Web/JavaScript/Reference/Errors/Resulting_string_too_large"
),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: "self-hosted",
sourceId: null,
lineNumber: NUMBER_REGEX,
columnNumber: 1,
functionName: "repeat",
},
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 1,
columnNumber: 5,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
@ -255,9 +441,33 @@ const expectedPageErrors = new Map([
errorMessageName: "JSMSG_PRECISION_RANGE",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl(
"docs/Web/JavaScript/Reference/Errors/Precision_range"
),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 1,
columnNumber: 9,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
@ -267,9 +477,24 @@ const expectedPageErrors = new Map([
errorMessageName: "JSMSG_STMT_AFTER_RETURN",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: false,
warning: true,
info: false,
sourceId: null,
lineText: "function a() { return; 1 + 1; }",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl(
"docs/Web/JavaScript/Reference/Errors/Stmt_after_return"
),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: null,
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
@ -279,9 +504,23 @@ const expectedPageErrors = new Map([
errorMessageName: "JSMSG_REDECLARED_VAR",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "{let a, a;}",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl(
"docs/Web/JavaScript/Reference/Errors/Redeclared_parameter"
),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: defaultStackFrames,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
notes: [
{
messageBody: /Previously declared at line/,
@ -302,9 +541,31 @@ const expectedPageErrors = new Map([
errorMessageName: "",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl("docs/Web/JavaScript/Reference/Errors/Read-only"),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 1,
columnNumber: 13,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
@ -314,24 +575,79 @@ const expectedPageErrors = new Map([
errorMessageName: "MSG_METHOD_THIS_DOES_NOT_IMPLEMENT_INTERFACE",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl("docs/Web/JavaScript/Reference/Errors/Read-only"),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 1,
columnNumber: 33,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: false,
isForwardedFromContentProcess: false,
},
],
[
`var error2 = new TypeError("abc");
error2.name = "MyPromiseError";
error2.message = "here2";
Promise.reject(error2)`,
`
function promiseThrow() {
var error2 = new TypeError("abc");
error2.name = "MyPromiseError";
error2.message = "here2";
return Promise.reject(error2);
}
promiseThrow()`,
{
errorMessage: /MyPromiseError: here2/,
errorMessageName: "",
sourceName: /test_page_errors/,
category: "content javascript",
timeStamp: /^\d+$/,
timeStamp: NUMBER_REGEX,
error: true,
warning: false,
info: false,
sourceId: null,
lineText: "",
lineNumber: NUMBER_REGEX,
columnNumber: NUMBER_REGEX,
exceptionDocURL: mdnUrl("docs/Web/JavaScript/Reference/Errors/Read-only"),
innerWindowID: NUMBER_REGEX,
private: false,
stacktrace: [
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 6,
columnNumber: 24,
functionName: "promiseThrow",
},
{
filename: /test_page_errors\.html/,
sourceId: null,
lineNumber: 8,
columnNumber: 7,
functionName: null,
},
...defaultStackFrames,
],
notes: null,
chromeContext: false,
isPromiseRejection: true,
isForwardedFromContentProcess: false,
[noUncaughtException]: true,
},
],

Просмотреть файл

@ -69,11 +69,11 @@ function checkObject(object, expected) {
function checkValue(name, value, expected) {
if (expected === null) {
ok(!value, "'" + name + "' is null");
is(value, null, `'${name}' is null`);
} else if (value === undefined) {
ok(false, "'" + name + "' is undefined");
is(value, undefined, `'${name}' is undefined`);
} else if (value === null) {
ok(false, "'" + name + "' is null");
is(value, expected, `'${name}' has expected value`);
} else if (
typeof expected == "string" ||
typeof expected == "number" ||
@ -84,7 +84,14 @@ function checkValue(name, value, expected) {
ok(expected.test(value), name + ": " + expected + " matched " + value);
} else if (Array.isArray(expected)) {
info("checking array for property '" + name + "'");
checkObject(value, expected);
ok(Array.isArray(value), `property '${name}' is an array`);
is(value.length, expected.length, "Array has expected length");
if (value.length !== expected.length) {
is(JSON.stringify(value, null, 2), JSON.stringify(expected, null, 2));
} else {
checkObject(value, expected);
}
} else if (typeof expected == "object") {
info("checking object for property '" + name + "'");
checkObject(value, expected);

Просмотреть файл

@ -0,0 +1,31 @@
/* 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";
// eslint-disable-next-line mozilla/reject-some-requires
loader.lazyRequireGetter(
this,
"getAdHocFrontOrPrimitiveGrip",
"devtools/client/fronts/object",
true
);
module.exports = function({ resource, targetFront }) {
if (resource?.pageError?.errorMessage) {
resource.pageError.errorMessage = getAdHocFrontOrPrimitiveGrip(
resource.pageError.errorMessage,
targetFront
);
}
if (resource?.pageError?.exception) {
resource.pageError.exception = getAdHocFrontOrPrimitiveGrip(
resource.pageError.exception,
targetFront
);
}
return resource;
};

Просмотреть файл

@ -3,5 +3,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'console-messages.js'
'console-messages.js',
'error-messages.js',
)