mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 09:07:54 +01:00
Reduce race conditions in scriptlet injection on Firefox
This is done by taking advantage through Firefox-specific contentScripts.register() API: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/contentScripts
This commit is contained in:
parent
f580cb414d
commit
4cac9d185b
1 changed files with 66 additions and 16 deletions
|
@ -19,6 +19,8 @@
|
|||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
/* globals browser */
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -61,6 +63,55 @@ const scriptletFilteringEngine = {
|
|||
},
|
||||
};
|
||||
|
||||
const contentScriptRegisterer = new (class {
|
||||
constructor() {
|
||||
this.hostnameToDetails = new Map();
|
||||
}
|
||||
register(hostname, code) {
|
||||
if ( browser.contentScripts === undefined ) { return false; }
|
||||
const details = this.hostnameToDetails.get(hostname);
|
||||
if ( details !== undefined ) {
|
||||
if ( code === details.code ) {
|
||||
return details.handle instanceof Promise === false;
|
||||
}
|
||||
details.handle.unregister();
|
||||
this.hostnameToDetails.delete(hostname);
|
||||
}
|
||||
const promise = browser.contentScripts.register({
|
||||
js: [ { code } ],
|
||||
allFrames: true,
|
||||
matches: [ `*://*.${hostname}/*` ],
|
||||
matchAboutBlank: true,
|
||||
runAt: 'document_start',
|
||||
}).then(handle => {
|
||||
this.hostnameToDetails.set(hostname, { handle, code });
|
||||
});
|
||||
this.hostnameToDetails.set(hostname, { handle: promise, code });
|
||||
return false;
|
||||
}
|
||||
unregister(hostname) {
|
||||
if ( this.hostnameToDetails.size === 0 ) { return; }
|
||||
const details = this.hostnameToDetails.get(hostname);
|
||||
if ( details === undefined ) { return; }
|
||||
this.hostnameToDetails.delete(hostname);
|
||||
this.unregisterHandle(details.handle);
|
||||
}
|
||||
reset() {
|
||||
if ( this.hostnameToDetails.size === 0 ) { return; }
|
||||
for ( const details of this.hostnameToDetails.values() ) {
|
||||
this.unregisterHandle(details.handle);
|
||||
}
|
||||
this.hostnameToDetails.clear();
|
||||
}
|
||||
unregisterHandle(handle) {
|
||||
if ( handle instanceof Promise ) {
|
||||
handle.then(handle => { handle.unregister(); });
|
||||
} else {
|
||||
handle.unregister();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Purpose of `contentscriptCode` below is too programmatically inject
|
||||
// content script code which only purpose is to inject scriptlets. This
|
||||
// essentially does the same as what uBO's declarative content script does,
|
||||
|
@ -141,7 +192,6 @@ const isolatedWorldInjector = (( ) => {
|
|||
return {
|
||||
parts,
|
||||
jsonSlot: parts.indexOf('json-slot'),
|
||||
scriptletSlot: parts.indexOf('scriptlet-slot'),
|
||||
assemble: function(hostname, scriptlets) {
|
||||
this.parts[this.jsonSlot] = JSON.stringify({ hostname });
|
||||
const code = this.parts.join('');
|
||||
|
@ -239,6 +289,7 @@ scriptletFilteringEngine.logFilters = function(tabId, url, filters) {
|
|||
scriptletFilteringEngine.reset = function() {
|
||||
scriptletDB.clear();
|
||||
duplicates.clear();
|
||||
contentScriptRegisterer.reset();
|
||||
scriptletCache.reset();
|
||||
acceptedCount = 0;
|
||||
discardedCount = 0;
|
||||
|
@ -441,25 +492,24 @@ scriptletFilteringEngine.injectNow = function(details) {
|
|||
request.domain = domainFromHostname(request.hostname);
|
||||
request.entity = entityFromDomain(request.domain);
|
||||
const scriptletDetails = this.retrieve(request);
|
||||
if ( scriptletDetails === undefined ) { return; }
|
||||
if ( scriptletDetails === undefined ) {
|
||||
contentScriptRegisterer.unregister(request.hostname);
|
||||
return;
|
||||
}
|
||||
const contentScript = [];
|
||||
if ( µb.hiddenSettings.debugScriptletInjector ) {
|
||||
contentScript.push('debugger');
|
||||
}
|
||||
const { mainWorld = '', isolatedWorld = '', filters } = scriptletDetails;
|
||||
if ( mainWorld !== '' ) {
|
||||
let code = mainWorldInjector.assemble(request.hostname, mainWorld, filters);
|
||||
if ( µb.hiddenSettings.debugScriptletInjector ) {
|
||||
code = 'debugger;\n' + code;
|
||||
}
|
||||
vAPI.tabs.executeScript(details.tabId, {
|
||||
code,
|
||||
frameId: details.frameId,
|
||||
matchAboutBlank: true,
|
||||
runAt: 'document_start',
|
||||
});
|
||||
contentScript.push(mainWorldInjector.assemble(request.hostname, mainWorld, filters));
|
||||
}
|
||||
if ( isolatedWorld !== '' ) {
|
||||
let code = isolatedWorldInjector.assemble(request.hostname, isolatedWorld);
|
||||
if ( µb.hiddenSettings.debugScriptletInjector ) {
|
||||
code = 'debugger;\n' + code;
|
||||
contentScript.push(isolatedWorldInjector.assemble(request.hostname, isolatedWorld));
|
||||
}
|
||||
const code = contentScript.join('\n\n');
|
||||
const isAlreadyInjected = contentScriptRegisterer.register(request.hostname, code);
|
||||
if ( isAlreadyInjected !== true ) {
|
||||
vAPI.tabs.executeScript(details.tabId, {
|
||||
code,
|
||||
frameId: details.frameId,
|
||||
|
|
Loading…
Reference in a new issue