284 lines
8.5 KiB
JavaScript
284 lines
8.5 KiB
JavaScript
let lastMessage;
|
|
const consoleListener = {
|
|
observe(message) {
|
|
dump(" >> new message: " + message.errorMessage + "\n");
|
|
lastMessage = message;
|
|
},
|
|
};
|
|
Services.console.registerListener(consoleListener);
|
|
|
|
// The Console Service notifies its listener after one event loop cycle.
|
|
// So wait for one tick after each action dispatching a message/error to the service.
|
|
function waitForATick() {
|
|
return new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
|
|
}
|
|
|
|
add_task(async function customScriptError() {
|
|
const scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
|
|
Ci.nsIScriptError
|
|
);
|
|
scriptError.init(
|
|
"foo",
|
|
"file.js",
|
|
1,
|
|
2,
|
|
Ci.nsIScriptError.warningFlag,
|
|
"some javascript"
|
|
);
|
|
Services.console.logMessage(scriptError);
|
|
|
|
await waitForATick();
|
|
|
|
Assert.equal(
|
|
lastMessage,
|
|
scriptError,
|
|
"We receive the exact same nsIScriptError object"
|
|
);
|
|
|
|
Assert.equal(lastMessage.errorMessage, "foo");
|
|
Assert.equal(lastMessage.sourceName, "file.js");
|
|
Assert.equal(lastMessage.lineNumber, 1);
|
|
Assert.equal(lastMessage.columnNumber, 2);
|
|
Assert.equal(lastMessage.flags, Ci.nsIScriptError.warningFlag);
|
|
Assert.equal(lastMessage.category, "some javascript");
|
|
|
|
Assert.equal(
|
|
lastMessage.stack,
|
|
undefined,
|
|
"Custom nsIScriptError object created from JS can't convey any stack"
|
|
);
|
|
});
|
|
|
|
add_task(async function callFunctionAndLogExceptionWithChromeGlobal() {
|
|
try {
|
|
ChromeUtils.callFunctionAndLogException(globalThis, function () {
|
|
throw new Error("custom exception");
|
|
});
|
|
Assert.ok(false, "callFunctionAndLogException should throw");
|
|
} catch (e) {
|
|
Assert.equal(
|
|
e.name,
|
|
"Error",
|
|
"callFunctionAndLogException thrown with the expected exception name"
|
|
);
|
|
Assert.equal(
|
|
e.message,
|
|
"custom exception",
|
|
"callFunctionAndLogException thrown with the expected message"
|
|
);
|
|
}
|
|
|
|
await waitForATick();
|
|
|
|
Assert.ok(!!lastMessage, "Got the message");
|
|
Assert.ok(
|
|
lastMessage instanceof Ci.nsIScriptError,
|
|
"This is a nsIScriptError"
|
|
);
|
|
|
|
Assert.equal(lastMessage.errorMessage, "Error: custom exception");
|
|
Assert.equal(lastMessage.sourceName, _TEST_FILE);
|
|
Assert.equal(lastMessage.lineNumber, 55);
|
|
Assert.equal(lastMessage.columnNumber, 13);
|
|
Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag);
|
|
Assert.equal(lastMessage.category, "chrome javascript");
|
|
Assert.ok(lastMessage.stack, "It has a stack");
|
|
Assert.equal(lastMessage.stack.source, _TEST_FILE);
|
|
Assert.equal(lastMessage.stack.line, 55);
|
|
Assert.equal(lastMessage.stack.column, 13);
|
|
Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame");
|
|
Assert.equal(
|
|
lastMessage.innerWindowID,
|
|
0,
|
|
"The message isn't bound to any WindowGlobal"
|
|
);
|
|
});
|
|
|
|
add_task(async function callFunctionAndLogExceptionWithContentGlobal() {
|
|
const window = createContentWindow();
|
|
try {
|
|
ChromeUtils.callFunctionAndLogException(window, function () {
|
|
throw new Error("another custom exception");
|
|
});
|
|
Assert.ok(false, "callFunctionAndLogException should throw");
|
|
} catch (e) {
|
|
Assert.equal(
|
|
e.name,
|
|
"Error",
|
|
"callFunctionAndLogException thrown with the expected exception name"
|
|
);
|
|
Assert.equal(
|
|
e.message,
|
|
"another custom exception",
|
|
"callFunctionAndLogException thrown with the expected message"
|
|
);
|
|
}
|
|
|
|
await waitForATick();
|
|
|
|
Assert.ok(!!lastMessage, "Got the message");
|
|
Assert.ok(
|
|
lastMessage instanceof Ci.nsIScriptError,
|
|
"This is a nsIScriptError"
|
|
);
|
|
|
|
Assert.equal(lastMessage.errorMessage, "Error: another custom exception");
|
|
Assert.equal(lastMessage.sourceName, _TEST_FILE);
|
|
Assert.equal(lastMessage.lineNumber, 101);
|
|
Assert.equal(lastMessage.columnNumber, 13);
|
|
Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag);
|
|
Assert.equal(lastMessage.category, "content javascript");
|
|
Assert.ok(lastMessage.stack, "It has a stack");
|
|
Assert.equal(lastMessage.stack.source, _TEST_FILE);
|
|
Assert.equal(lastMessage.stack.line, 101);
|
|
Assert.equal(lastMessage.stack.column, 13);
|
|
Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame");
|
|
Assert.ok(
|
|
!!window.windowGlobalChild.innerWindowId,
|
|
"The window has a innerWindowId"
|
|
);
|
|
Assert.equal(
|
|
lastMessage.innerWindowID,
|
|
window.windowGlobalChild.innerWindowId,
|
|
"The message is bound to the content window"
|
|
);
|
|
});
|
|
|
|
add_task(async function callFunctionAndLogExceptionForContentScriptSandboxes() {
|
|
const { sandbox, window } = createContentScriptSandbox();
|
|
Cu.evalInSandbox(
|
|
`function foo() { throw new Error("sandbox exception"); }`,
|
|
sandbox,
|
|
null,
|
|
"sandbox-file.js",
|
|
1,
|
|
0
|
|
);
|
|
try {
|
|
ChromeUtils.callFunctionAndLogException(window, sandbox.foo);
|
|
Assert.fail("callFunctionAndLogException should throw");
|
|
} catch (e) {
|
|
Assert.equal(
|
|
e.name,
|
|
"Error",
|
|
"callFunctionAndLogException thrown with the expected exception name"
|
|
);
|
|
Assert.equal(
|
|
e.message,
|
|
"sandbox exception",
|
|
"callFunctionAndLogException thrown with the expected message"
|
|
);
|
|
}
|
|
|
|
await waitForATick();
|
|
|
|
Assert.ok(!!lastMessage, "Got the message");
|
|
// Note that it is important to "instanceof" in order to expose the nsIScriptError attributes.
|
|
Assert.ok(
|
|
lastMessage instanceof Ci.nsIScriptError,
|
|
"This is a nsIScriptError"
|
|
);
|
|
|
|
Assert.equal(lastMessage.errorMessage, "Error: sandbox exception");
|
|
Assert.equal(lastMessage.sourceName, "sandbox-file.js");
|
|
Assert.equal(lastMessage.lineNumber, 1);
|
|
Assert.equal(lastMessage.columnNumber, 24);
|
|
Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag);
|
|
Assert.equal(lastMessage.category, "content javascript");
|
|
Assert.ok(lastMessage.stack, "It has a stack");
|
|
Assert.equal(lastMessage.stack.source, "sandbox-file.js");
|
|
Assert.equal(lastMessage.stack.line, 1);
|
|
Assert.equal(lastMessage.stack.column, 24);
|
|
Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame");
|
|
Assert.ok(
|
|
!!window.windowGlobalChild.innerWindowId,
|
|
"The sandbox's prototype is a window and has a innerWindowId"
|
|
);
|
|
Assert.equal(
|
|
lastMessage.innerWindowID,
|
|
window.windowGlobalChild.innerWindowId,
|
|
"The message is bound to the sandbox's prototype WindowGlobal"
|
|
);
|
|
});
|
|
|
|
add_task(
|
|
async function callFunctionAndLogExceptionForContentScriptSandboxesWrappedInChrome() {
|
|
const { sandbox, window } = createContentScriptSandbox();
|
|
Cu.evalInSandbox(
|
|
`function foo() { throw new Error("sandbox exception"); }`,
|
|
sandbox,
|
|
null,
|
|
"sandbox-file.js",
|
|
1,
|
|
0
|
|
);
|
|
try {
|
|
ChromeUtils.callFunctionAndLogException(window, function () {
|
|
sandbox.foo();
|
|
});
|
|
Assert.fail("callFunctionAndLogException should throw");
|
|
} catch (e) {
|
|
Assert.equal(
|
|
e.name,
|
|
"Error",
|
|
"callFunctionAndLogException thrown with the expected exception name"
|
|
);
|
|
Assert.equal(
|
|
e.message,
|
|
"sandbox exception",
|
|
"callFunctionAndLogException thrown with the expected message"
|
|
);
|
|
}
|
|
|
|
await waitForATick();
|
|
|
|
Assert.ok(!!lastMessage, "Got the message");
|
|
// Note that it is important to "instanceof" in order to expose the nsIScriptError attributes.
|
|
Assert.ok(
|
|
lastMessage instanceof Ci.nsIScriptError,
|
|
"This is a nsIScriptError"
|
|
);
|
|
|
|
Assert.ok(
|
|
!!window.windowGlobalChild.innerWindowId,
|
|
"The sandbox's prototype is a window and has a innerWindowId"
|
|
);
|
|
Assert.equal(
|
|
lastMessage.innerWindowID,
|
|
window.windowGlobalChild.innerWindowId,
|
|
"The message is bound to the sandbox's prototype WindowGlobal"
|
|
);
|
|
}
|
|
);
|
|
|
|
add_task(function teardown() {
|
|
Services.console.unregisterListener(consoleListener);
|
|
});
|
|
|
|
// We are in xpcshell, so we can't have a real DOM Window as in Firefox
|
|
// but let's try to have a fake one.
|
|
function createContentWindow() {
|
|
const principal =
|
|
Services.scriptSecurityManager.createContentPrincipalFromOrigin(
|
|
"http://example.com/"
|
|
);
|
|
|
|
const webnav = Services.appShell.createWindowlessBrowser(false);
|
|
|
|
webnav.docShell.createAboutBlankDocumentViewer(principal, principal);
|
|
|
|
return webnav.document.defaultView;
|
|
}
|
|
|
|
// Create a Sandbox as in WebExtension content scripts
|
|
function createContentScriptSandbox() {
|
|
const window = createContentWindow();
|
|
// The sandboxPrototype is the key here in order to
|
|
// make xpc::SandboxWindowOrNull ignore the sandbox
|
|
// and instead retrieve its prototype and link the error message
|
|
// to the window instead of the sandbox.
|
|
return {
|
|
sandbox: Cu.Sandbox(window, { sandboxPrototype: window }),
|
|
window,
|
|
};
|
|
}
|