Skip to content

Commit 4cf7b23

Browse files
committed
Simpler and more reliable sendSyncMessage implementation and usage.
1 parent dcc779b commit 4cf7b23

4 files changed

Lines changed: 32 additions & 67 deletions

File tree

src/bg/RequestGuard.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,10 @@ var RequestGuard = (() => {
444444
capabilities && !capabilities.has("script"));
445445
}
446446
let header = csp.patchHeaders(responseHeaders, capabilities);
447+
/*
448+
// Uncomment me to disable networking-level CSP for debugging purposes
449+
header = null;
450+
*/
447451
if (header) {
448452
pending.cspHeader = header;
449453
debug(`CSP blocker on %s:`, url, header.value);

src/content/DocumentCSP.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ class DocumentCSP {
88

99
apply(capabilities, embedding = CSP.isEmbedType(this.document.contentType)) {
1010
let {document} = this;
11+
if (!capabilities.has("script")) {
12+
// safety net for XML (especially SVG) documents and synchronous scripts running
13+
// while inserting the CSP <meta> element.
14+
document.defaultView.addEventListener("beforescriptexecute", e => {
15+
if (!e.isTrusted) return;
16+
e.preventDefault();
17+
debug("Fallback beforexecutescript listener blocked ", e.target);
18+
}, true);
19+
}
1120
if (!(document instanceof HTMLDocument)) {
1221
// this is not HTML, hence we cannot inject a <meta> CSP
13-
if (!capabilities.has("script")) {
14-
// safety net for XML (especially SVG) documents
15-
document.defaultView.addEventListener("beforescriptexecute",
16-
e => e.preventDefault(), true);
17-
}
1822
return false;
1923
}
2024
let csp = this.builder;

src/content/staticNS.js

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,6 @@
8282
error(e, "Could not setup local policy", localPolicy);
8383
}
8484
}
85-
86-
addEventListener("beforescriptexecute", e => {
87-
if (!e.isTrusted) return;
88-
// safety net for synchronous loads on Firefox
89-
if (!this.canScript) {
90-
e.preventDefault();
91-
let script = e.target;
92-
blockedScripts.push(script)
93-
log("Some script managed to be inserted in the DOM while fetching policy, blocking it.\n", script);
94-
}
95-
}, true);
9685
}
9786

9887
let policy = null;
@@ -125,8 +114,8 @@
125114
for (;;) {
126115
try {
127116
policy = browser.runtime.sendSyncMessage(
128-
{id: "fetchPolicy", url, contextUrl: url},
129-
{callback: setup, canScript: () => ns.canScript});
117+
{id: "fetchPolicy", url, contextUrl: url},
118+
setup);
130119
break;
131120
} catch (e) {
132121
if (!Messages.isMissingEndpoint(e)) {

src/lib/SyncMessage.js

Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
let unsuspend = result => {
2222
pending.delete(id);
2323
if (wrapper.unsuspend) {
24-
setTimeout(wrapper.unsuspend, 0);
24+
wrapper.unsuspend();
2525
}
2626
return result;
2727
}
@@ -31,7 +31,11 @@
3131
unsuspend();
3232
throw e;
3333
}
34-
console.debug("sendSyncMessage: returning", result);
34+
/*
35+
// Uncomment me to add artificial delay for debugging purposes
36+
let tmpResult = result;
37+
result = new Promise(resolve => setTimeout(() => resolve(tmpResult), 500));
38+
*/
3539
return (result instanceof Promise ? result
3640
: new Promise(resolve => resolve(result))
3741
).then(result => unsuspend(result));
@@ -61,14 +65,14 @@
6165
let wrapper = pending.get(msgId);
6266
if (!wrapper.unsuspend) {
6367
wrapper.unsuspend = resolve;
64-
return;
6568
} else {
6669
let {unsuspend} = wrapper;
6770
wrapper.unsuspend = () => {
6871
unsuspend();
6972
resolve();
7073
}
7174
}
75+
return;
7276
}
7377
resolve();
7478
}).then(() => ret("go on"))
@@ -201,20 +205,6 @@
201205
let uuid = () => (Math.random() * Date.now()).toString(16);
202206
let docUrl = document.URL;
203207
browser.runtime.sendSyncMessage = (msg, callback) => {
204-
// we interrogate the canScript() callback to know whether the caller
205-
// wants scripts deferred by sendSyncMessage to be eventually executed:
206-
// - undefined -> too soon to tell, suspend
207-
// - true -> go on and execute
208-
// - false -> block
209-
let canScript;
210-
if (callback && typeof callback === "object") {
211-
({canScript, callback} = callback);
212-
}
213-
if (typeof canScript !== "function") {
214-
// if no canScript() callback was passed, default to execute scripts
215-
canScript = () => true;
216-
}
217-
218208
let msgId = `${uuid()},${docUrl}`;
219209
let url = `${ENDPOINT_PREFIX}id=${encodeURIComponent(msgId)}` +
220210
`&url=${encodeURIComponent(docUrl)}`;
@@ -225,16 +215,19 @@
225215
}
226216

227217
if (MOZILLA) {
218+
228219
// In order to cope with inconsistencies in XHR synchronicity,
229220
// allowing scripts to be executed (especially with synchronous loads
230221
// or when other extensions manipulate the DOM early) we additionally
231222
// suspend on beforescriptexecute events
232223

233224
let suspendURL = url + "&suspend=true";
234225
let suspended = 0;
226+
let suspendedId = 0;
235227
let suspend = () => {
236228
suspended++;
237-
console.debug("Suspended, count:", suspended)
229+
let id = suspendedId++;
230+
console.debug("sendSyncMessage suspend #%s/%s", id, suspended);
238231
try {
239232
let r = new XMLHttpRequest();
240233
r.open("GET", suspendURL, false);
@@ -243,63 +236,38 @@
243236
console.error(e);
244237
}
245238
suspended--;
246-
console.debug("Unsuspended, count: ", suspended);
239+
console.debug("sendSyncMessage resume #%s/%s", id, suspended);
247240
};
248241

249-
let onBeforeScript = e => {
250-
if(typeof canScript() === "undefined") {
251-
suspend();
252-
}
253-
let allowed = canScript();
254-
if (typeof allowed === "undefined") {
255-
console.error("sendSyncMessage: script unsuspended before canScript() is defined!", e.target);
256-
}
257-
if (!allowed) {
258-
console.debug("sendSyncMessage blocked a script element", e.target);
259-
e.preventDefault();
260-
}
261-
};
262242

263-
addEventListener("beforescriptexecute", onBeforeScript, true);
243+
264244
let domSuspender = new MutationObserver(records => {
245+
console.debug("sendSyncMessage suspending on ", records)
265246
suspend();
266247
});
267248
domSuspender.observe(document.documentElement, {childList: true});
268249

269250

270-
271251
let finalize = () => {
252+
console.debug("sendSyncMessage finalizing");
272253
domSuspender.disconnect();
273-
removeEventListener("beforescriptexecute", onBeforeScript, true);
274254
};
275255

276256
// on Firefox we first need to send an async message telling the
277257
// background script about the tab ID, which does not get sent
278258
// with "privileged" XHR
279259
let result;
260+
280261
browser.runtime.sendMessage(
281262
{__syncMessage__: {id: msgId, payload: msg}}
282263
).then(r => {
283264
result = r;
284265
if (callback) callback(r);
266+
finalize();
285267
}).catch(e => {
286268
throw e;
287269
});
288270

289-
290-
291-
if (callback) {
292-
let realCB = callback;
293-
callback = r => {
294-
try {
295-
realCB(r);
296-
} finally {
297-
finalize();
298-
}
299-
};
300-
return;
301-
}
302-
303271
try {
304272
suspend();
305273
} finally {

0 commit comments

Comments
 (0)