Source code
Revision control
Copy as Markdown
Other Tools
const workerURL =
/**
* An Error can be a JS Error or a DOMException. The primary difference is that
* JS Errors have a SpiderMonkey specific `fileName` for the filename and
* DOMEXCEPTION uses `filename`.
*/
function normalizeError(err) {
if (!err) {
return null;
}
const isDOMException = "filename" in err;
return {
message: err.message,
name: err.name,
isDOMException,
code: err.code,
// normalize to fileName
fileName: isDOMException ? err.filename : err.fileName,
hasFileName: !!err.fileName,
hasFilename: !!err.filename,
lineNumber: err.lineNumber,
columnNumber: err.columnNumber,
stack: err.stack,
stringified: err.toString(),
};
}
function normalizeErrorEvent(event) {
if (!event) {
return null;
}
return {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: normalizeError(event.error),
stringified: event.toString(),
};
}
/**
* Normalize the `OnErrorEventHandlerNonNull onerror` invocation. The
* special handling in JSEventHandler::HandleEvent ends up spreading out the
* contents of the ErrorEvent into discrete arguments. The one thing lost is
* we can't toString the ScriptEvent itself, but this should be the same as the
* message anyways.
*
* The spec for the invocation is the "special error event handling" logic
* described in step 4 at:
* noting that the step somewhat glosses over that it's only "onerror" that is
* OnErrorEventHandlerNonNull and capable of processing 5 arguments and that's
* why an addEventListener "error" listener doesn't get this handling.
*
* Argument names here are made to match the call-site in JSEventHandler.
*/
function normalizeOnError(
msgOrEvent,
fileName,
lineNumber,
columnNumber,
error
) {
return {
message: msgOrEvent,
filename: fileName,
lineno: lineNumber,
colno: columnNumber,
error: normalizeError(error),
stringified: null,
};
}
/**
* Helper to postMessage the provided data after a setTimeout(0) so that any
* error event currently being dispatched that will bubble to our parent will
* be delivered before our postMessage.
*/
function delayedPostMessage(data) {
setTimeout(() => {
postMessage(data);
}, 0);
}
onmessage = function (a) {
const args = a.data;
// Messages are either nested (forward to a nested worker) or should be
// processed locally.
if (a.data.nested) {
const worker = new Worker(workerURL);
let firstErrorEvent;
// When the test mode is "catch"
worker.onmessage = function (event) {
delayedPostMessage({
nestedMessage: event.data,
errorEvent: firstErrorEvent,
});
};
worker.onerror = function (event) {
firstErrorEvent = normalizeErrorEvent(event);
event.preventDefault();
};
a.data.nested = false;
worker.postMessage(a.data);
return;
}
// Local test.
if (a.data.mode === "catch") {
try {
importScripts(a.data.url);
workerMethod();
} catch (ex) {
delayedPostMessage({
args,
error: normalizeError(ex),
});
}
} else if (a.data.mode === "uncaught") {
const onerrorPromise = new Promise(resolve => {
self.onerror = (...onerrorArgs) => {
resolve(normalizeOnError(...onerrorArgs));
};
});
const listenerPromise = new Promise(resolve => {
self.addEventListener("error", evt => {
resolve(normalizeErrorEvent(evt));
});
});
Promise.all([onerrorPromise, listenerPromise]).then(
([onerrorEvent, listenerEvent]) => {
delayedPostMessage({
args,
onerrorEvent,
listenerEvent,
});
}
);
importScripts(a.data.url);
workerMethod();
// we will have thrown by this point, which will trigger an "error" event
// on our global and then will propagate to our parent (which could be a
// window or a worker, if nested).
//
// To avoid hangs, throw a different error here that will fail equivalence
// tests.
throw new Error("We expected an error and this is a failsafe for hangs.");
}
};