--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -267,39 +267,43 @@ class BaseContext {
runSafe(callback, ...args) {
return this.applySafe(callback, args);
}
runSafeWithoutClone(callback, ...args) {
return this.applySafeWithoutClone(callback, args);
}
- applySafe(callback, args) {
+ applySafe(callback, args, caller) {
if (this.unloaded) {
- Cu.reportError("context.runSafe called after context unloaded");
+ Cu.reportError("context.runSafe called after context unloaded",
+ caller);
} else if (!this.active) {
- Cu.reportError("context.runSafe called while context is inactive");
+ Cu.reportError("context.runSafe called while context is inactive",
+ caller);
} else {
try {
let {cloneScope} = this;
args = args.map(arg => Cu.cloneInto(arg, cloneScope));
} catch (e) {
Cu.reportError(e);
dump(`runSafe failure: cloning into ${this.cloneScope}: ${e}\n\n${filterStack(Error())}`);
}
- return this.applySafeWithoutClone(callback, args);
+ return this.applySafeWithoutClone(callback, args, caller);
}
}
- applySafeWithoutClone(callback, args) {
+ applySafeWithoutClone(callback, args, caller) {
if (this.unloaded) {
- Cu.reportError("context.runSafeWithoutClone called after context unloaded");
+ Cu.reportError("context.runSafeWithoutClone called after context unloaded",
+ caller);
} else if (!this.active) {
- Cu.reportError("context.runSafeWithoutClone called while context is inactive");
+ Cu.reportError("context.runSafeWithoutClone called while context is inactive",
+ caller);
} else {
try {
return Reflect.apply(callback, null, args);
} catch (e) {
dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(Error())}]]\n`);
Cu.reportError(e);
}
}
@@ -407,32 +411,44 @@ class BaseContext {
/**
* 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.
*
* @param {object} error An object with a `message` property. May
* optionally be an `Error` object belonging to the target scope.
+ * @param {SavedFrame?} caller
+ * The optional caller frame which triggered this callback, to be used
+ * in error reporting.
* @param {function} callback The callback to call.
* @returns {*} The return value of callback.
*/
- withLastError(error, callback) {
+ withLastError(error, caller, callback) {
this.lastError = this.normalizeError(error);
try {
return callback();
} finally {
if (!this.checkedLastError) {
- Cu.reportError(`Unchecked lastError value: ${this.lastError}`);
+ Cu.reportError(`Unchecked lastError value: ${this.lastError}`, caller);
}
this.lastError = null;
}
}
/**
+ * Captures the most recent stack frame which belongs to the extension.
+ *
+ * @returns {SavedFrame?}
+ */
+ getCaller() {
+ return ChromeUtils.getCallerLocation(this.principal);
+ }
+
+ /**
* Wraps the given promise so it can be safely returned to extension
* code in this context.
*
* If `callback` is provided, however, it is used as a completion
* function for the promise, and no promise is returned. In this case,
* the callback is called when the promise resolves or rejects. In the
* latter case, `lastError` is set to the rejection value, and the
* callback function must check `browser.runtime.lastError` or
@@ -448,71 +464,83 @@ class BaseContext {
* `callback` function or resolving the wrapped promise.
*
* @param {function} [callback] The callback function to wrap
*
* @returns {Promise|undefined} If callback is null, a promise object
* belonging to the target scope. Otherwise, undefined.
*/
wrapPromise(promise, callback = null) {
+ let caller = this.getCaller();
let applySafe = this.applySafe.bind(this);
if (Cu.getGlobalForObject(promise) === this.cloneScope) {
applySafe = this.applySafeWithoutClone.bind(this);
}
if (callback) {
promise.then(
args => {
if (this.unloaded) {
- dump(`Promise resolved after context unloaded\n`);
+ Cu.reportError(`Promise resolved after context unloaded\n`,
+ caller);
} else if (!this.active) {
- dump(`Promise resolved while context is inactive\n`);
+ Cu.reportError(`Promise resolved while context is inactive\n`,
+ caller);
} else if (args instanceof NoCloneSpreadArgs) {
- this.applySafeWithoutClone(callback, args.unwrappedValues);
+ this.applySafeWithoutClone(callback, args.unwrappedValues, caller);
} else if (args instanceof SpreadArgs) {
- applySafe(callback, args);
+ applySafe(callback, args, caller);
} else {
- applySafe(callback, [args]);
+ applySafe(callback, [args], caller);
}
},
error => {
- this.withLastError(error, () => {
+ this.withLastError(error, caller, () => {
if (this.unloaded) {
- dump(`Promise rejected after context unloaded\n`);
+ Cu.reportError(`Promise rejected after context unloaded\n`,
+ caller);
} else if (!this.active) {
- dump(`Promise rejected while context is inactive\n`);
+ Cu.reportError(`Promise rejected while context is inactive\n`,
+ caller);
} else {
- this.applySafeWithoutClone(callback, []);
+ this.applySafeWithoutClone(callback, [], caller);
}
});
});
} else {
return new this.cloneScope.Promise((resolve, reject) => {
promise.then(
value => {
if (this.unloaded) {
- dump(`Promise resolved after context unloaded\n`);
+ Cu.reportError(`Promise resolved after context unloaded\n`,
+ caller);
} else if (!this.active) {
- dump(`Promise resolved while context is inactive\n`);
+ Cu.reportError(`Promise resolved while context is inactive\n`,
+ caller);
} else if (value instanceof NoCloneSpreadArgs) {
let values = value.unwrappedValues;
- this.applySafeWithoutClone(resolve, values.length == 1 ? [values[0]] : [values]);
+ this.applySafeWithoutClone(resolve, values.length == 1 ? [values[0]] : [values],
+ caller);
} else if (value instanceof SpreadArgs) {
- applySafe(resolve, value.length == 1 ? value : [value]);
+ applySafe(resolve, value.length == 1 ? value : [value],
+ caller);
} else {
- applySafe(resolve, [value]);
+ applySafe(resolve, [value], caller);
}
},
value => {
if (this.unloaded) {
- dump(`Promise rejected after context unloaded: ${value && value.message}\n`);
+ Cu.reportError(`Promise rejected after context unloaded: ${value && value.message}\n`,
+ caller);
} else if (!this.active) {
- dump(`Promise rejected while context is inactive: ${value && value.message}\n`);
+ 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);
}
});
});
}
}
unload() {
this.unloaded = true;