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.
This commit is contained in:
Raymond Hill 2025-04-11 09:35:38 -04:00
parent 3d2f70ac56
commit 15e832da8a
No known key found for this signature in database
GPG key ID: 25E1490B761470C2

View file

@ -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) {