Skip to content

Commit 85d9e57

Browse files
committed
Bug 1655624 - Improve reliability of onMessage's error handling r=zombie
Bug 1655624 happened because the format of an internal error changed, which caused an internal error to be propagated unexpectedly. This patch fixes the issue by only propagating errors that are known to originate from extensions, plus a regression test. This patch also fixes a few other issues: - Internal errors are redacted to "An unexpected error occurred", which partially fixes bug 1643176. - Fix minor regression in void rejections: Prior to bug 1583484, an onMessage handler that rejected with a void value would cause sendMessage to reject. Since bug 1583484 the promise is not rejected, as the error is inadvertently ignored due to a runtime error: "TypeError: can't access property "result", err is undefined". - Avoid type confusion of objects with the mozWebExtLocation member. Differential Revision: https://phabricator.services.mozilla.com/D85643 UltraBlame original commit: 46abf66e3b2090e7ebcde77aefd81982689cc148
1 parent 68f262e commit 85d9e57

File tree

5 files changed

+113
-18
lines changed

5 files changed

+113
-18
lines changed

toolkit/components/extensions/ConduitsParent.jsm

+10-3
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const EXPORTED_SYMBOLS = ["BroadcastConduit", "ConduitsParent"];
4949

5050

5151
const {
52-
ExtensionUtils: { DefaultWeakMap },
52+
ExtensionUtils: { DefaultWeakMap, ExtensionError },
5353
} = ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
5454

5555
const { BaseConduit } = ChromeUtils.import(
@@ -288,8 +288,15 @@ class BroadcastConduit extends BaseConduit {
288288
result = value;
289289
}
290290
})
291-
292-
.catch(err => err.result !== Cr.NS_ERROR_NOT_AVAILABLE && reject(err))
291+
.catch(err => {
292+
293+
294+
if (err instanceof ExtensionError || err?.mozWebExtLocation) {
295+
reject(err);
296+
} else {
297+
Cu.reportError(err);
298+
}
299+
})
293300
);
294301

295302
Promise.allSettled(promises).then(() => resolve(result));

toolkit/components/extensions/ExtensionChild.jsm

+31-12
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const { ExtensionUtils } = ChromeUtils.import(
5353
"resource://gre/modules/ExtensionUtils.jsm"
5454
);
5555

56-
const { DefaultMap, LimitedSet, getUniqueId } = ExtensionUtils;
56+
const { DefaultMap, ExtensionError, LimitedSet, getUniqueId } = ExtensionUtils;
5757

5858
const {
5959
EventEmitter,
@@ -124,6 +124,15 @@ const ExtensionActivityLogChild = {
124124

125125

126126

127+
class ExtensionErrorHolder {
128+
constructor(trustedErrorObject) {
129+
this.trustedErrorObject = trustedErrorObject;
130+
}
131+
}
132+
133+
134+
135+
127136

128137

129138
const StrongPromise = {
@@ -145,7 +154,7 @@ const StrongPromise = {
145154
observe(subject, topic, id) {
146155
let message = "Promised response from onMessage listener went out of scope";
147156
let { reject, location } = this.stillAlive.get(id);
148-
reject({ message, mozWebExtLocation: location });
157+
reject(new ExtensionErrorHolder({ message, mozWebExtLocation: location }));
149158
this.stillAlive.delete(id);
150159
},
151160
};
@@ -183,7 +192,19 @@ class MessageEvent extends SimpleEventAPI {
183192

184193
return !responses.length
185194
? { received: true, response: false }
186-
: Promise.race(responses).then(value => ({ response: true, value }));
195+
: Promise.race(responses).then(
196+
value => ({ response: true, value }),
197+
error => Promise.reject(this.unwrapOrSanitizeError(error))
198+
);
199+
}
200+
201+
unwrapOrSanitizeError(error) {
202+
if (error instanceof ExtensionErrorHolder) {
203+
return error.trustedErrorObject;
204+
}
205+
206+
207+
return new ExtensionError(error?.message ?? "An unexpected error occurred");
187208
}
188209

189210
wrapResponse(fire, message, sender) {
@@ -306,15 +327,13 @@ class Messenger {
306327
}
307328

308329
sendRuntimeMessage({ extensionId, message, callback, ...args }) {
309-
let response = this.conduit
310-
.queryRuntimeMessage({
311-
extensionId: extensionId || this.context.extension.id,
312-
holder: holdMessage(message),
313-
...args,
314-
})
315-
.catch(({ message, mozWebExtLocation }) =>
316-
Promise.reject({ message, mozWebExtLocation })
317-
);
330+
let response = this.conduit.queryRuntimeMessage({
331+
extensionId: extensionId || this.context.extension.id,
332+
holder: holdMessage(message),
333+
...args,
334+
});
335+
336+
318337
return this.context.wrapPromise(response, callback);
319338
}
320339

toolkit/components/extensions/ExtensionParent.jsm

+4-3
Original file line numberDiff line numberDiff line change
@@ -329,9 +329,10 @@ const ProxyMessenger = {
329329
arg.firstResponse = true;
330330
let kind = await this.normalizeArgs(arg, sender);
331331
let result = await this.conduit.castRuntimeMessage(kind, arg);
332-
return result
333-
? result.value
334-
: Promise.reject({ message: ERROR_NO_RECEIVERS });
332+
if (!result) {
333+
throw new ExtensionError(ERROR_NO_RECEIVERS);
334+
}
335+
return result.value;
335336
},
336337

337338
async recvPortConnect(arg, { sender }) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
2+
3+
"use strict";
4+
5+
6+
7+
8+
add_task(async function onMessage_ignores_destroyed_contexts() {
9+
let extension = ExtensionTestUtils.loadExtension({
10+
background() {
11+
browser.test.onMessage.addListener(async msg => {
12+
if (msg !== "startTest") {
13+
return;
14+
}
15+
try {
16+
let res = await browser.runtime.sendMessage("msg_from_bg");
17+
browser.test.assertEq(0, res, "Result from onMessage");
18+
browser.test.notifyPass("handled_onMessage");
19+
} catch (e) {
20+
browser.test.fail(`Unexpected error: ${e.message} :: ${e.stack}`);
21+
browser.test.notifyFail("handled_onMessage");
22+
}
23+
});
24+
},
25+
files: {
26+
"tab.html": `
27+
<!DOCTYPE html><meta charset="utf-8">
28+
<script src="tab.js"></script>
29+
`,
30+
"tab.js": () => {
31+
let where = location.search.slice(1);
32+
let resolveOnMessage;
33+
browser.runtime.onMessage.addListener(async msg => {
34+
browser.test.assertEq("msg_from_bg", msg, `onMessage at ${where}`);
35+
browser.test.sendMessage(`received:${where}`);
36+
return new Promise(resolve => {
37+
resolveOnMessage = resolve;
38+
});
39+
});
40+
browser.test.onMessage.addListener(msg => {
41+
if (msg === `resolveOnMessage:${where}`) {
42+
resolveOnMessage(0);
43+
}
44+
});
45+
},
46+
},
47+
});
48+
await extension.startup();
49+
let tabToCloseEarly = await ExtensionTestUtils.loadContentPage(
50+
`moz-extension://${extension.uuid}/tab.html?tabToCloseEarly`,
51+
{ extension }
52+
);
53+
let tabToRespond = await ExtensionTestUtils.loadContentPage(
54+
`moz-extension://${extension.uuid}/tab.html?tabToRespond`,
55+
{ extension }
56+
);
57+
extension.sendMessage("startTest");
58+
await Promise.all([
59+
extension.awaitMessage("received:tabToCloseEarly"),
60+
extension.awaitMessage("received:tabToRespond"),
61+
]);
62+
await tabToCloseEarly.close();
63+
extension.sendMessage("resolveOnMessage:tabToRespond");
64+
await extension.awaitFinish("handled_onMessage");
65+
await tabToRespond.close();
66+
await extension.unload();
67+
});

toolkit/components/extensions/test/xpcshell/xpcshell-common.ini

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ skip-if = ccov && os == 'linux' # bug 1607581
138138
[test_ext_runtime_ports.js]
139139
[test_ext_runtime_sendMessage.js]
140140
[test_ext_runtime_sendMessage_errors.js]
141+
[test_ext_runtime_sendMessage_multiple.js]
141142
[test_ext_runtime_sendMessage_no_receiver.js]
142143
[test_ext_same_site_cookies.js]
143144
[test_ext_same_site_redirects.js]

0 commit comments

Comments
 (0)