From 314784079c96124334df0589a0fc5b1c63893b61 Mon Sep 17 00:00:00 2001 From: hackademix Date: Sat, 19 Oct 2019 19:55:26 +0200 Subject: [PATCH] More robust SyncMessage implementation coping with XHR suspension inconsistencies on Firefox. --- src/lib/SyncMessage.js | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/lib/SyncMessage.js b/src/lib/SyncMessage.js index 5b6ac6e..1df9411 100644 --- a/src/lib/SyncMessage.js +++ b/src/lib/SyncMessage.js @@ -34,12 +34,23 @@ } let msg = params.get("msg"); let documentUrl = params.get("url"); + let suspension = !!params.get("suspend"); let sender; - if (tabId === TAB_ID_NONE) { + if (tabId === TAB_ID_NONE || suspension) { // Firefox sends privileged content script XHR without valid tab ids // so we cache sender info from unprivileged XHR correlated by msgId if (pending.has(msgId)) { sender = pending.get(msgId); + if (suspension) { // we hold any script execution / DOM modification on this promise + return new Promise(resolve => { + sender.unsuspend = resolve; + }); + } + if (sender.unsuspend) { + let {unsuspend} = sender; + delete sender.unsuspend; + setTimeout(unsuspend(ret("unsuspend")), 0); + } pending.delete(msgId); } else { throw new Error(`sendSyncMessage: cannot correlate sender info for ${msgId}.`); @@ -141,9 +152,7 @@ } } else if (typeof browser.runtime.sendSyncMessage !== "function") { // Content Script side - if (typeof uuid !== "function") { - let uuid = () => (Math.random() * Date.now()).toString(16); - } + let uuid = () => (Math.random() * Date.now()).toString(16); let docUrl = document.URL; browser.runtime.sendSyncMessage = msg => { let msgId = `${uuid()},${docUrl}`; @@ -154,12 +163,30 @@ // about frameAncestors url += "&top=true"; } - + let finalizers = []; if (MOZILLA) { // on Firefox we first need to send an async message telling the // background script about the tab ID, which does not get sent // with "privileged" XHR browser.runtime.sendMessage({___syncMessageId: msgId}); + + // In order to cope with inconsistencies in XHR synchronicity, + // allowing DOM element to be inserted and script to be executed + // (seen with file:// and ftp:// loads) we additionally suspend on + // Mutation notifications and beforescriptexecute events + let suspendURL = url + "&suspend"; + let suspend = () => { + let r = new XMLHttpRequest(); + r.open("GET", url, false); + r.send(null); + }; + let domSuspender = new MutationObserver(suspend); + domSuspender.observe(document.documentElement, {childList: true}); + addEventListener("beforescriptexecute", suspend, true); + finalizers.push(() => { + removeEventListener("beforescriptexecute", suspend, true); + domSuspender.disconnect(); + }); } // then we send the payload using a privileged XHR, which is not subject // to CORS but unfortunately doesn't carry any tab id except on Chromium @@ -172,6 +199,8 @@ return JSON.parse(r.responseText); } catch(e) { console.error(`syncMessage error in ${document.URL}: ${e.message} (response ${r.responseText})`); + } finally { + for (let f of finalizers) try { f(); } catch(e) { console.error(e); } } return null; };