From 15e832da8a1dc074a21bf567691ebd01056b06d9 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 11 Apr 2025 09:35:38 -0400 Subject: [PATCH] Mind potential race condition when dynamically registering scriptlets In Firefox, scriptlets are dynamically registered as content scripts to ensure they execute in a timely manner. The race condition could lead to scriptlet injection failing at browser launch time in Firefox when the setting "Suspend network activity until all filter lists are loaded" had been disabled[1], even after forcing a page reload. Causing the filter lists to reload would make the issue go away. [1] Default is enabled in Firefox and it is strongly advised to NOT change this. --- src/js/scriptlet-filtering.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/js/scriptlet-filtering.js b/src/js/scriptlet-filtering.js index 89a5ec92c..f71b07c2f 100644 --- a/src/js/scriptlet-filtering.js +++ b/src/js/scriptlet-filtering.js @@ -38,6 +38,7 @@ import µb from './background.js'; /******************************************************************************/ const contentScriptRegisterer = { + id: 1, hostnameToDetails: new Map(), register(hostname, code) { if ( browser.contentScripts === undefined ) { return false; } @@ -47,9 +48,10 @@ const contentScriptRegisterer = { if ( code === details.code ) { return details.handle instanceof Promise === false; } - details.handle.unregister(); + this.unregisterHandle(details.handle); this.hostnameToDetails.delete(hostname); } + const id = this.id++; const promise = browser.contentScripts.register({ js: [ { code } ], allFrames: true, @@ -57,12 +59,14 @@ const contentScriptRegisterer = { matchAboutBlank: true, runAt: 'document_start', }).then(handle => { - this.hostnameToDetails.set(hostname, { handle, code }); - return handle; + const details = this.hostnameToDetails.get(hostname); + if ( details === undefined ) { return; } + if ( details.id !== id ) { return; } + details.handle = handle; }).catch(( ) => { this.hostnameToDetails.delete(hostname); }); - this.hostnameToDetails.set(hostname, { handle: promise, code }); + this.hostnameToDetails.set(hostname, { id, handle: promise, code }); return false; }, unregister(hostname) {