2022-09-16 21:56:35 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
uBlock Origin - a browser extension to block requests.
|
|
|
|
Copyright (C) 2022-present Raymond Hill
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
|
|
|
|
Home: https://github.com/gorhill/uBlock
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* jshint esversion:11 */
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
import { browser, dnr } from './ext.js';
|
|
|
|
import { fetchJSON } from './fetch.js';
|
2022-09-27 13:46:24 +02:00
|
|
|
import { getAllTrustedSiteDirectives } from './trusted-sites.js';
|
2022-09-20 14:24:01 +02:00
|
|
|
|
2022-09-28 01:51:38 +02:00
|
|
|
import * as ut from './utils.js';
|
2022-09-16 21:56:35 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2022-09-18 15:31:44 +02:00
|
|
|
let scriptingDetailsPromise;
|
2022-09-16 21:56:35 +02:00
|
|
|
|
2022-09-18 15:31:44 +02:00
|
|
|
function getScriptingDetails() {
|
|
|
|
if ( scriptingDetailsPromise !== undefined ) {
|
|
|
|
return scriptingDetailsPromise;
|
2022-09-16 21:56:35 +02:00
|
|
|
}
|
2022-09-18 15:31:44 +02:00
|
|
|
scriptingDetailsPromise = fetchJSON('/rulesets/scripting-details').then(entries => {
|
2022-09-30 01:51:33 +02:00
|
|
|
const out = new Map();
|
|
|
|
for ( const entry of entries ) {
|
|
|
|
out.set(entry[0], new Map(entry[1]));
|
2022-09-18 15:31:44 +02:00
|
|
|
}
|
|
|
|
return out;
|
2022-09-16 21:56:35 +02:00
|
|
|
});
|
2022-09-18 15:31:44 +02:00
|
|
|
return scriptingDetailsPromise;
|
2022-09-16 21:56:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2022-09-30 01:51:33 +02:00
|
|
|
const toRegisterable = (fname, hostnames) => {
|
2022-09-16 21:56:35 +02:00
|
|
|
const directive = {
|
|
|
|
id: fname,
|
2022-09-30 01:51:33 +02:00
|
|
|
allFrames: true,
|
|
|
|
matchOriginAsFallback: true,
|
2022-09-16 21:56:35 +02:00
|
|
|
};
|
2022-09-30 01:51:33 +02:00
|
|
|
if ( hostnames ) {
|
|
|
|
directive.matches = ut.matchesFromHostnames(hostnames);
|
2022-09-16 21:56:35 +02:00
|
|
|
} else {
|
2022-09-28 01:51:38 +02:00
|
|
|
directive.matches = [ '<all_urls>' ];
|
2022-09-16 21:56:35 +02:00
|
|
|
}
|
2022-09-20 14:24:01 +02:00
|
|
|
directive.js = [ `/rulesets/js/${fname.slice(0,2)}/${fname.slice(2)}.js` ];
|
2022-09-30 01:51:33 +02:00
|
|
|
if ( (ut.fidFromFileName(fname) & RUN_AT_END_BIT) !== 0 ) {
|
2022-09-24 17:33:04 +02:00
|
|
|
directive.runAt = 'document_end';
|
|
|
|
} else {
|
|
|
|
directive.runAt = 'document_start';
|
|
|
|
}
|
2022-09-28 01:51:38 +02:00
|
|
|
if ( (ut.fidFromFileName(fname) & MAIN_WORLD_BIT) !== 0 ) {
|
2022-09-16 21:56:35 +02:00
|
|
|
directive.world = 'MAIN';
|
|
|
|
}
|
|
|
|
return directive;
|
|
|
|
};
|
|
|
|
|
2022-09-30 01:51:33 +02:00
|
|
|
const RUN_AT_END_BIT = 0b10;
|
2022-09-24 17:33:04 +02:00
|
|
|
const MAIN_WORLD_BIT = 0b01;
|
2022-09-20 02:19:55 +02:00
|
|
|
|
2022-09-19 14:55:45 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2022-09-30 01:51:33 +02:00
|
|
|
// Important: We need to sort the arrays for fast comparison
|
|
|
|
const arrayEq = (a, b) => {
|
|
|
|
if ( a === undefined ) { return b === undefined; }
|
|
|
|
if ( b === undefined ) { return false; }
|
|
|
|
const alen = a.length;
|
|
|
|
if ( alen !== b.length ) { return false; }
|
|
|
|
a.sort(); b.sort();
|
|
|
|
for ( let i = 0; i < alen; i++ ) {
|
|
|
|
if ( a[i] !== b[i] ) { return false; }
|
2022-09-17 17:22:25 +02:00
|
|
|
}
|
2022-09-30 01:51:33 +02:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const shouldUpdate = (registered, candidateHostnames) => {
|
|
|
|
const registeredHostnames = registered.matches &&
|
|
|
|
ut.hostnamesFromMatches(registered.matches);
|
|
|
|
return arrayEq(registeredHostnames, candidateHostnames) === false;
|
2022-09-17 17:22:25 +02:00
|
|
|
};
|
|
|
|
|
2022-09-28 01:51:38 +02:00
|
|
|
const isTrustedHostname = (trustedSites, hn) => {
|
|
|
|
if ( trustedSites.size === 0 ) { return false; }
|
|
|
|
while ( hn ) {
|
|
|
|
if ( trustedSites.has(hn) ) { return true; }
|
|
|
|
hn = ut.toBroaderHostname(hn);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2022-09-16 21:56:35 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2022-09-17 14:26:41 +02:00
|
|
|
async function getInjectableCount(origin) {
|
2022-09-28 01:51:38 +02:00
|
|
|
const url = ut.parsedURLromOrigin(origin);
|
2022-09-17 14:26:41 +02:00
|
|
|
if ( url === undefined ) { return 0; }
|
2022-09-16 21:56:35 +02:00
|
|
|
|
|
|
|
const [
|
|
|
|
rulesetIds,
|
2022-09-18 15:31:44 +02:00
|
|
|
scriptingDetails,
|
2022-09-16 21:56:35 +02:00
|
|
|
] = await Promise.all([
|
|
|
|
dnr.getEnabledRulesets(),
|
2022-09-18 15:31:44 +02:00
|
|
|
getScriptingDetails(),
|
2022-09-16 21:56:35 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
let total = 0;
|
|
|
|
|
|
|
|
for ( const rulesetId of rulesetIds ) {
|
2022-09-30 01:51:33 +02:00
|
|
|
const hostnamesToFidsMap = scriptingDetails.get(rulesetId);
|
|
|
|
if ( hostnamesToFidsMap === undefined ) { continue; }
|
2022-09-18 15:31:44 +02:00
|
|
|
let hn = url.hostname;
|
|
|
|
while ( hn !== '' ) {
|
2022-09-30 01:51:33 +02:00
|
|
|
const fids = hostnamesToFidsMap.get(hn);
|
2022-09-20 14:24:01 +02:00
|
|
|
if ( typeof fids === 'number' ) {
|
|
|
|
total += 1;
|
|
|
|
} else if ( Array.isArray(fids) ) {
|
|
|
|
total += fids.length;
|
|
|
|
}
|
2022-09-28 01:51:38 +02:00
|
|
|
hn = ut.toBroaderHostname(hn);
|
2022-09-16 21:56:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2022-09-28 01:51:38 +02:00
|
|
|
function registerSomeInjectables(args) {
|
|
|
|
const {
|
|
|
|
hostnamesSet,
|
2022-09-27 13:46:24 +02:00
|
|
|
trustedSites,
|
2022-09-16 21:56:35 +02:00
|
|
|
rulesetIds,
|
2022-09-18 15:31:44 +02:00
|
|
|
scriptingDetails,
|
2022-09-28 01:51:38 +02:00
|
|
|
} = args;
|
2022-09-16 21:56:35 +02:00
|
|
|
|
2022-09-28 01:51:38 +02:00
|
|
|
const toRegisterMap = new Map();
|
2022-09-27 13:46:24 +02:00
|
|
|
|
2022-09-30 01:51:33 +02:00
|
|
|
const checkMatches = (hostnamesToFidsMap, hn) => {
|
|
|
|
let fids = hostnamesToFidsMap.get(hn);
|
2022-09-20 02:19:55 +02:00
|
|
|
if ( fids === undefined ) { return; }
|
2022-09-20 14:24:01 +02:00
|
|
|
if ( typeof fids === 'number' ) { fids = [ fids ]; }
|
2022-09-20 02:19:55 +02:00
|
|
|
for ( const fid of fids ) {
|
2022-09-28 01:51:38 +02:00
|
|
|
const fname = ut.fnameFromFileId(fid);
|
2022-09-30 01:51:33 +02:00
|
|
|
let existing = toRegisterMap.get(fname);
|
2022-09-18 15:31:44 +02:00
|
|
|
if ( existing ) {
|
2022-09-30 01:51:33 +02:00
|
|
|
if ( existing[0] === '*' ) { continue; }
|
|
|
|
existing.push(hn);
|
2022-09-20 14:24:01 +02:00
|
|
|
} else {
|
2022-09-30 01:51:33 +02:00
|
|
|
toRegisterMap.set(fname, existing = [ hn ]);
|
2022-09-20 14:24:01 +02:00
|
|
|
}
|
2022-09-30 01:51:33 +02:00
|
|
|
if ( hn !== '*' ) { continue; }
|
|
|
|
existing.length = 0;
|
|
|
|
existing.push('*');
|
|
|
|
break;
|
2022-09-20 14:24:01 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for ( const rulesetId of rulesetIds ) {
|
2022-09-30 01:51:33 +02:00
|
|
|
const hostnamesToFidsMap = scriptingDetails.get(rulesetId);
|
|
|
|
if ( hostnamesToFidsMap === undefined ) { continue; }
|
2022-09-28 01:51:38 +02:00
|
|
|
for ( let hn of hostnamesSet ) {
|
|
|
|
if ( isTrustedHostname(trustedSites, hn) ) { continue; }
|
2022-09-27 13:46:24 +02:00
|
|
|
while ( hn ) {
|
2022-09-30 01:51:33 +02:00
|
|
|
checkMatches(hostnamesToFidsMap, hn);
|
2022-09-28 01:51:38 +02:00
|
|
|
hn = ut.toBroaderHostname(hn);
|
2022-09-20 14:24:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-28 01:51:38 +02:00
|
|
|
return toRegisterMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
function registerAllInjectables(args) {
|
|
|
|
const {
|
|
|
|
trustedSites,
|
|
|
|
rulesetIds,
|
|
|
|
scriptingDetails,
|
|
|
|
} = args;
|
|
|
|
|
|
|
|
const toRegisterMap = new Map();
|
2022-09-17 14:26:41 +02:00
|
|
|
|
2022-09-16 21:56:35 +02:00
|
|
|
for ( const rulesetId of rulesetIds ) {
|
2022-09-30 01:51:33 +02:00
|
|
|
const hostnamesToFidsMap = scriptingDetails.get(rulesetId);
|
|
|
|
if ( hostnamesToFidsMap === undefined ) { continue; }
|
|
|
|
for ( let [ hn, fids ] of hostnamesToFidsMap ) {
|
2022-09-28 01:51:38 +02:00
|
|
|
if ( isTrustedHostname(trustedSites, hn) ) { continue; }
|
|
|
|
if ( typeof fids === 'number' ) { fids = [ fids ]; }
|
|
|
|
for ( const fid of fids ) {
|
|
|
|
const fname = ut.fnameFromFileId(fid);
|
2022-09-30 01:51:33 +02:00
|
|
|
let existing = toRegisterMap.get(fname);
|
2022-09-28 01:51:38 +02:00
|
|
|
if ( existing ) {
|
2022-09-30 01:51:33 +02:00
|
|
|
if ( existing[0] === '*' ) { continue; }
|
|
|
|
existing.push(hn);
|
2022-09-28 01:51:38 +02:00
|
|
|
} else {
|
2022-09-30 01:51:33 +02:00
|
|
|
toRegisterMap.set(fname, existing = [ hn ]);
|
2022-09-28 01:51:38 +02:00
|
|
|
}
|
2022-09-30 01:51:33 +02:00
|
|
|
if ( hn !== '*' ) { continue; }
|
|
|
|
existing.length = 0;
|
|
|
|
existing.push('*');
|
|
|
|
break;
|
2022-09-16 21:56:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-28 01:51:38 +02:00
|
|
|
return toRegisterMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
async function registerInjectables(origins) {
|
|
|
|
void origins;
|
|
|
|
|
|
|
|
if ( browser.scripting === undefined ) { return false; }
|
|
|
|
|
|
|
|
const [
|
|
|
|
hostnamesSet,
|
|
|
|
trustedSites,
|
|
|
|
rulesetIds,
|
|
|
|
scriptingDetails,
|
|
|
|
registered,
|
|
|
|
] = await Promise.all([
|
|
|
|
browser.permissions.getAll(),
|
|
|
|
getAllTrustedSiteDirectives(),
|
|
|
|
dnr.getEnabledRulesets(),
|
|
|
|
getScriptingDetails(),
|
|
|
|
browser.scripting.getRegisteredContentScripts(),
|
|
|
|
]).then(results => {
|
|
|
|
results[0] = new Set(ut.hostnamesFromMatches(results[0].origins));
|
|
|
|
results[1] = new Set(results[1]);
|
|
|
|
return results;
|
|
|
|
});
|
|
|
|
|
|
|
|
const toRegisterMap = hostnamesSet.has('*')
|
|
|
|
? registerAllInjectables({
|
|
|
|
trustedSites,
|
|
|
|
rulesetIds,
|
|
|
|
scriptingDetails,
|
|
|
|
})
|
|
|
|
: registerSomeInjectables({
|
|
|
|
hostnamesSet,
|
|
|
|
trustedSites,
|
|
|
|
rulesetIds,
|
|
|
|
scriptingDetails,
|
|
|
|
});
|
|
|
|
|
2022-09-17 17:22:25 +02:00
|
|
|
const before = new Map(registered.map(entry => [ entry.id, entry ]));
|
|
|
|
|
2022-09-16 21:56:35 +02:00
|
|
|
const toAdd = [];
|
2022-09-17 17:22:25 +02:00
|
|
|
const toUpdate = [];
|
2022-09-30 01:51:33 +02:00
|
|
|
for ( const [ fname, hostnames ] of toRegisterMap ) {
|
2022-09-17 17:22:25 +02:00
|
|
|
if ( before.has(fname) === false ) {
|
2022-09-30 01:51:33 +02:00
|
|
|
toAdd.push(toRegisterable(fname, hostnames));
|
2022-09-17 17:22:25 +02:00
|
|
|
continue;
|
|
|
|
}
|
2022-09-30 01:51:33 +02:00
|
|
|
if ( shouldUpdate(before.get(fname), hostnames) ) {
|
|
|
|
toUpdate.push(toRegisterable(fname, hostnames));
|
2022-09-17 17:22:25 +02:00
|
|
|
}
|
2022-09-16 21:56:35 +02:00
|
|
|
}
|
2022-09-17 17:22:25 +02:00
|
|
|
|
2022-09-16 21:56:35 +02:00
|
|
|
const toRemove = [];
|
2022-09-17 17:22:25 +02:00
|
|
|
for ( const fname of before.keys() ) {
|
2022-09-28 01:51:38 +02:00
|
|
|
if ( toRegisterMap.has(fname) ) { continue; }
|
2022-09-16 21:56:35 +02:00
|
|
|
toRemove.push(fname);
|
|
|
|
}
|
|
|
|
|
|
|
|
const todo = [];
|
|
|
|
if ( toRemove.length !== 0 ) {
|
2022-09-17 17:22:25 +02:00
|
|
|
console.info(`Unregistered ${toRemove} content (css/js)`);
|
2022-09-30 01:51:33 +02:00
|
|
|
todo.push(
|
|
|
|
browser.scripting.unregisterContentScripts({ ids: toRemove })
|
|
|
|
.catch(reason => { console.info(reason); })
|
|
|
|
);
|
2022-09-16 21:56:35 +02:00
|
|
|
}
|
|
|
|
if ( toAdd.length !== 0 ) {
|
2022-09-17 17:22:25 +02:00
|
|
|
console.info(`Registered ${toAdd.map(v => v.id)} content (css/js)`);
|
2022-09-30 01:51:33 +02:00
|
|
|
todo.push(
|
|
|
|
browser.scripting.registerContentScripts(toAdd)
|
|
|
|
.catch(reason => { console.info(reason); })
|
|
|
|
);
|
2022-09-17 17:22:25 +02:00
|
|
|
}
|
|
|
|
if ( toUpdate.length !== 0 ) {
|
|
|
|
console.info(`Updated ${toUpdate.map(v => v.id)} content (css/js)`);
|
2022-09-30 01:51:33 +02:00
|
|
|
todo.push(
|
|
|
|
browser.scripting.updateContentScripts(toUpdate)
|
|
|
|
.catch(reason => { console.info(reason); })
|
|
|
|
);
|
2022-09-16 21:56:35 +02:00
|
|
|
}
|
|
|
|
if ( todo.length === 0 ) { return; }
|
|
|
|
|
|
|
|
return Promise.all(todo);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
export {
|
|
|
|
getInjectableCount,
|
2022-09-28 01:51:38 +02:00
|
|
|
registerInjectables
|
2022-09-16 21:56:35 +02:00
|
|
|
};
|