Bug 1441333: Part 5 - Use proper async caller location in normalizeError. r=zombie
Currently, when we create an error object at the end of an aysnc operation, we
only get a useful caller location if async stacks are enabled.
This patch changes our behavior to use the saved caller location we've already
stored when creating an Error object based on a plain string message.
MozReview-Commit-ID: DDO0lAUHYRO
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2895,16 +2895,17 @@ XPCJSRuntime::Initialize(JSContext* cx)
mPrevGCSliceCallback = JS::SetGCSliceCallback(cx, GCSliceCallback);
mPrevDoCycleCollectionCallback = JS::SetDoCycleCollectionCallback(cx,
DoCycleCollectionCallback);
JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
JS_AddWeakPointerZonesCallback(cx, WeakPointerZonesCallback, this);
JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback, this);
JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
js::SetPreserveWrapperCallback(cx, PreserveWrapper);
+ JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals);
JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback);
JS_SetSetUseCounterCallback(cx, SetUseCounterCallback);
js::SetWindowProxyClass(cx, &OuterWindowProxyClass);
js::SetXrayJitInfo(&gXrayJitInfo);
JS::SetProcessLargeAllocationFailureCallback(OnLargeAllocationFailureCallback);
// The JS engine needs to keep the source code around in order to implement
// Function.prototype.toSource(). It'd be nice to not have to do this for
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -376,17 +376,17 @@ class Messenger {
sendMessage(messageManager, msg, recipient, responseCallback) {
let holder = new StructuredCloneHolder(msg);
let promise = this._sendMessage(messageManager, "Extension:Message", holder, recipient)
.catch(error => {
if (error.result == MessageChannel.RESULT_NO_HANDLER) {
return Promise.reject({message: "Could not establish connection. Receiving end does not exist."});
} else if (error.result != MessageChannel.RESULT_NO_RESPONSE) {
- return Promise.reject({message: error.message});
+ return Promise.reject(error);
}
});
holder = null;
return this.context.wrapPromise(promise, responseCallback);
}
sendNativeMessage(messageManager, msg, recipient, responseCallback) {
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -384,33 +384,46 @@ class BaseContext {
* the target is an error object which belongs to that scope, it is
* returned as-is. If it is an ordinary object with a `message`
* property, it is converted into an error belonging to the target
* scope. If it is an Error object which does *not* belong to the
* clone scope, it is reported, and converted to an unexpected
* exception error.
*
* @param {Error|object} error
+ * @param {SavedFrame?} [caller]
* @returns {Error}
*/
- normalizeError(error) {
+ normalizeError(error, caller) {
if (error instanceof this.cloneScope.Error) {
return error;
}
let message, fileName;
- if (error && typeof error === "object" &&
- (ChromeUtils.getClassName(error) === "Object" ||
- error instanceof ExtensionError ||
- this.principal.subsumes(Cu.getObjectPrincipal(error)))) {
- message = error.message;
- fileName = error.fileName;
- } else {
+ if (error && typeof error === "object") {
+ const isPlain = ChromeUtils.getClassName(error) === "Object";
+ if (isPlain && error.mozWebExtLocation) {
+ caller = error.mozWebExtLocation;
+ }
+ if (isPlain && caller) {
+ caller = Cu.cloneInto(caller, this.cloneScope);
+ return ChromeUtils.createError(error.message, caller);
+ }
+
+ if (isPlain ||
+ error instanceof ExtensionError ||
+ this.principal.subsumes(Cu.getObjectPrincipal(error))) {
+ message = error.message;
+ fileName = error.fileName;
+ }
+ }
+
+ if (!message) {
Cu.reportError(error);
+ message = "An unexpected error occurred";
}
- message = message || "An unexpected error occurred";
return new this.cloneScope.Error(message, fileName);
}
/**
* Sets the value of `.lastError` to `error`, calls the given
* callback, and reports an error if the value has not been checked
* when the callback returns.
*
@@ -529,17 +542,17 @@ class BaseContext {
value => {
if (this.unloaded) {
Cu.reportError(`Promise rejected after context unloaded: ${value && value.message}\n`,
caller);
} else if (!this.active) {
Cu.reportError(`Promise rejected while context is inactive: ${value && value.message}\n`,
caller);
} else {
- this.applySafeWithoutClone(reject, [this.normalizeError(value)],
+ this.applySafeWithoutClone(reject, [this.normalizeError(value, caller)],
caller);
}
});
});
}
}
unload() {
--- a/toolkit/components/extensions/MessageChannel.jsm
+++ b/toolkit/components/extensions/MessageChannel.jsm
@@ -968,17 +968,18 @@ this.MessageChannel = {
if (error && typeof(error) == "object") {
if (error.result) {
response.result = error.result;
}
// Error objects are not structured-clonable, so just copy
// over the important properties.
for (let key of ["fileName", "filename", "lineNumber",
- "columnNumber", "message", "stack", "result"]) {
+ "columnNumber", "message", "stack", "result",
+ "mozWebExtLocation"]) {
if (key in error) {
response.error[key] = error[key];
}
}
}
target.sendAsyncMessage(MESSAGE_RESPONSE, response);
})