diff --git a/platform/mv3/extension/js/scripting-manager.js b/platform/mv3/extension/js/scripting-manager.js index 65ea39974..94818d403 100644 --- a/platform/mv3/extension/js/scripting-manager.js +++ b/platform/mv3/extension/js/scripting-manager.js @@ -432,76 +432,6 @@ function registerScriptlet(context, scriptletDetails) { /******************************************************************************/ -function registerScriptletEntity(context) { - // https://bugzilla.mozilla.org/show_bug.cgi?id=1736575 - // `MAIN` world not yet supported in Firefox - if ( isGecko ) { return; } - - const { before, filteringModeDetails, rulesetsDetails } = context; - - const js = []; - for ( const details of rulesetsDetails ) { - const { scriptlets } = details; - if ( scriptlets instanceof Object === false ) { continue; } - if ( Array.isArray(scriptlets.entityBasedTokens) === false ) { continue; } - if ( scriptlets.entityBasedTokens.length === 0 ) { continue; } - for ( const token of scriptlets.entityBasedTokens ) { - js.push(`/rulesets/scripting/scriptlet-entity/${details.id}.${token}.js`); - } - } - - if ( js.length === 0 ) { return; } - - const matches = []; - const excludeMatches = []; - if ( filteringModeDetails.extendedGeneric.has('all-urls') ) { - excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.none)); - excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.network)); - excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.extendedSpecific)); - matches.push(''); - } else { - matches.push( - ...ut.matchesFromHostnames(filteringModeDetails.extendedGeneric) - ); - } - - if ( matches.length === 0 ) { return; } - - const registered = before.get('scriptlet.entity'); - before.delete('scriptlet.entity'); // Important! - - // register - if ( registered === undefined ) { - context.toAdd.push({ - id: 'scriptlet.entity', - js, - allFrames: true, - matches, - excludeMatches, - runAt: 'document_start', - world: 'MAIN', - }); - return; - } - - // update - const directive = { id: 'scriptlet.entity' }; - if ( arrayEq(registered.js, js, false) === false ) { - directive.js = js; - } - if ( arrayEq(registered.matches, matches) === false ) { - directive.matches = matches; - } - if ( arrayEq(registered.excludeMatches, excludeMatches) === false ) { - directive.excludeMatches = excludeMatches; - } - if ( directive.js || directive.matches || directive.excludeMatches ) { - context.toUpdate.push(directive); - } -} - -/******************************************************************************/ - function registerSpecific(context, specificDetails) { const { filteringModeDetails } = context; @@ -749,7 +679,6 @@ async function registerInjectables(origins) { registerDeclarative(context, declarativeDetails); registerProcedural(context, proceduralDetails); registerScriptlet(context, scriptletDetails); - registerScriptletEntity(context); registerSpecific(context, specificDetails); registerSpecificEntity(context); registerGeneric(context, genericDetails); diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 2368ecde5..7e615eb24 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -32,6 +32,7 @@ import redirectResourcesMap from './js/redirect-resources.js'; import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js'; import { fnameFromFileId } from './js/utils.js'; import * as sfp from './js/static-filtering-parser.js'; +import * as makeScriptlet from './make-scriptlets.js'; /******************************************************************************/ @@ -872,261 +873,21 @@ async function processProceduralCosmeticFilters(assetDetails, mapin) { async function processScriptletFilters(assetDetails, mapin) { if ( mapin === undefined ) { return; } - const { domainBased, entityBased } = splitDomainAndEntity(mapin); + makeScriptlet.init(); - // Load all available scriptlets into a key-val map, where the key is the - // scriptlet token, and val is the whole content of the file. - const originalScriptletMap = await loadAllSourceScriptlets(); - - let domainBasedTokens; - if ( domainBased.size !== 0 ) { - domainBasedTokens = await processDomainScriptletFilters(assetDetails, domainBased, originalScriptletMap); + for ( const details of mapin.values() ) { + makeScriptlet.compile(details, assetDetails.isTrusted); } - let entityBasedTokens; - if ( entityBased.size !== 0 ) { - entityBasedTokens = await processEntityScriptletFilters(assetDetails, entityBased, originalScriptletMap); + const stats = await makeScriptlet.commit( + assetDetails.id, + `${scriptletDir}/scriptlet`, + writeFile + ); + if ( stats.length !== 0 ) { + scriptletStats.set(assetDetails.id, stats); } - - return { domainBasedTokens, entityBasedTokens }; -} - -/******************************************************************************/ - -const parseScriptletArguments = raw => { - const out = []; - let s = raw; - let len = s.length; - let beg = 0, pos = 0; - let i = 1; - while ( beg < len ) { - pos = s.indexOf(',', pos); - // Escaped comma? If so, skip. - if ( pos > 0 && s.charCodeAt(pos - 1) === 0x5C /* '\\' */ ) { - s = s.slice(0, pos - 1) + s.slice(pos); - len -= 1; - continue; - } - if ( pos === -1 ) { pos = len; } - out.push(s.slice(beg, pos).trim()); - beg = pos = pos + 1; - i++; - } - return out; -}; - -const parseScriptletFilter = (raw, scriptletMap, tokenSuffix = '') => { - const filter = raw.slice(4, -1); - const end = filter.length; - let pos = filter.indexOf(','); - if ( pos === -1 ) { pos = end; } - const parts = filter.trim().split(',').map(s => s.trim()); - const token = scriptletDealiasingMap.get(parts[0]) || ''; - if ( token === '' ) { return; } - if ( scriptletMap.has(`${token}${tokenSuffix}`) === false ) { return; } - return { - token, - args: parseScriptletArguments(parts.slice(1).join(',').trim()), - }; -}; - -/******************************************************************************/ - -async function processDomainScriptletFilters(assetDetails, domainBased, originalScriptletMap) { - // For each instance of distinct scriptlet, we will collect distinct - // instances of arguments, and for each distinct set of argument, we - // will collect the set of hostnames for which the scriptlet/args is meant - // to execute. This will allow us a single content script file and the - // scriptlets execution will depend on hostname testing against the - // URL of the document at scriptlet execution time. In the end, we - // should have no more generated content script per subscription than the - // number of distinct source scriptlets. - const scriptletDetails = new Map(); - const rejectedFilters = []; - for ( const [ rawFilter, entry ] of domainBased ) { - if ( entry.rejected ) { - rejectedFilters.push(rawFilter); - continue; - } - const normalized = parseScriptletFilter(rawFilter, originalScriptletMap); - if ( normalized === undefined ) { - log(`Discarded unsupported scriptlet filter: ${rawFilter}`, true); - continue; - } - let argsDetails = scriptletDetails.get(normalized.token); - if ( argsDetails === undefined ) { - argsDetails = new Map(); - scriptletDetails.set(normalized.token, argsDetails); - } - const argsHash = JSON.stringify(normalized.args); - let hostnamesDetails = argsDetails.get(argsHash); - if ( hostnamesDetails === undefined ) { - hostnamesDetails = { - a: normalized.args, - y: new Set(), - n: new Set(), - }; - argsDetails.set(argsHash, hostnamesDetails); - } - if ( entry.matches ) { - for ( const hn of entry.matches ) { - hostnamesDetails.y.add(hn); - } - } - if ( entry.excludeMatches ) { - for ( const hn of entry.excludeMatches ) { - hostnamesDetails.n.add(hn); - } - } - } - - log(`Rejected scriptlet filters: ${rejectedFilters.length}`); - log(rejectedFilters.map(line => `\t${line}`).join('\n'), true); - - const generatedFiles = []; - const tokens = []; - - for ( const [ token, argsDetails ] of scriptletDetails ) { - const argsMap = Array.from(argsDetails).map(entry => [ - uidint32(entry[0]), - { a: entry[1].a, n: entry[1].n } - ]); - const hostnamesMap = new Map(); - for ( const [ argsHash, details ] of argsDetails ) { - scriptletHostnameToIdMap(details.y, uidint32(argsHash), hostnamesMap); - } - - const argsList = argsMap2List(argsMap, hostnamesMap); - const patchedScriptlet = originalScriptletMap.get(token) - .replace( - '$rulesetId$', - assetDetails.id - ).replace( - /\bself\.\$argsList\$/m, - `${JSON.stringify(argsList, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$hostnamesMap\$/m, - `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` - ); - const fname = `${assetDetails.id}.${token}.js`; - const fpath = `${scriptletDir}/scriptlet/${fname}`; - writeFile(fpath, patchedScriptlet); - generatedFiles.push(fname); - tokens.push(token); - - const hostnameMatches = new Set(hostnamesMap.keys()); - if ( hostnameMatches.has('*') ) { - hostnameMatches.clear(); - hostnameMatches.add('*'); - } - let rulesetScriptlets = scriptletStats.get(assetDetails.id); - if ( rulesetScriptlets === undefined ) { - scriptletStats.set(assetDetails.id, rulesetScriptlets = []); - } - rulesetScriptlets.push([ token, Array.from(hostnameMatches).sort() ]); - } - - if ( generatedFiles.length !== 0 ) { - const scriptletFilterCount = Array.from(scriptletDetails.values()) - .reduce((a, b) => a + b.size, 0); - log(`Scriptlet-related distinct filters: ${scriptletFilterCount}`); - log(`Scriptlet-related injectable files: ${generatedFiles.length}`); - log(`\t${generatedFiles.join(', ')}`); - } - - return tokens; -} - -/******************************************************************************/ - -async function processEntityScriptletFilters(assetDetails, entityBased, originalScriptletMap) { - // For each instance of distinct scriptlet, we will collect distinct - // instances of arguments, and for each distinct set of argument, we - // will collect the set of hostnames for which the scriptlet/args is meant - // to execute. This will allow us a single content script file and the - // scriptlets execution will depend on hostname testing against the - // URL of the document at scriptlet execution time. In the end, we - // should have no more generated content script per subscription than the - // number of distinct source scriptlets. - const scriptletMap = new Map(); - const rejectedFilters = []; - for ( const [ rawFilter, entry ] of entityBased ) { - if ( entry.rejected ) { - rejectedFilters.push(rawFilter); - continue; - } - const normalized = parseScriptletFilter(rawFilter, originalScriptletMap, '.entity'); - if ( normalized === undefined ) { - log(`Discarded unsupported scriptlet filter: ${rawFilter}`, true); - continue; - } - let argsDetails = scriptletMap.get(normalized.token); - if ( argsDetails === undefined ) { - argsDetails = new Map(); - scriptletMap.set(normalized.token, argsDetails); - } - const argsHash = JSON.stringify(normalized.args); - let scriptletDetails = argsDetails.get(argsHash); - if ( scriptletDetails === undefined ) { - scriptletDetails = { - a: normalized.args, - y: new Set(), - n: new Set(), - }; - argsDetails.set(argsHash, scriptletDetails); - } - if ( entry.matches ) { - for ( const entity of entry.matches ) { - scriptletDetails.y.add(entity); - } - } - if ( entry.excludeMatches ) { - for ( const hn of entry.excludeMatches ) { - scriptletDetails.n.add(hn); - } - } - } - - log(`Rejected scriptlet filters: ${rejectedFilters.length}`); - log(rejectedFilters.map(line => `\t${line}`).join('\n'), true); - - const generatedFiles = []; - const tokens = []; - - for ( const [ token, argsDetails ] of scriptletMap ) { - const argsMap = Array.from(argsDetails).map(entry => [ - uidint32(entry[0]), - { a: entry[1].a, n: entry[1].n } - ]); - const entitiesMap = new Map(); - for ( const [ argsHash, details ] of argsDetails ) { - scriptletHostnameToIdMap(details.y, uidint32(argsHash), entitiesMap); - } - - const argsList = argsMap2List(argsMap, entitiesMap); - const patchedScriptlet = originalScriptletMap.get(`${token}.entity`) - .replace( - '$rulesetId$', - assetDetails.id - ).replace( - /\bself\.\$argsList\$/m, - `${JSON.stringify(argsList, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$entitiesMap\$/m, - `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` - ); - const fname = `${assetDetails.id}.${token}.js`; - const fpath = `${scriptletDir}/scriptlet-entity/${fname}`; - writeFile(fpath, patchedScriptlet); - generatedFiles.push(fname); - tokens.push(token); - } - - if ( generatedFiles.length !== 0 ) { - log(`Scriptlet-related entity-based injectable files: ${generatedFiles.length}`); - log(`\t${generatedFiles.join(', ')}`); - } - - return tokens; + makeScriptlet.reset(); + return { domainBasedTokens: stats.length }; } /******************************************************************************/ @@ -1306,6 +1067,7 @@ async function main() { enabled: true, urls: contentURLs, homeURL: 'https://github.com/uBlockOrigin/uAssets', + isTrusted: true, }); // Regional rulesets diff --git a/platform/mv3/make-scriptlets.js b/platform/mv3/make-scriptlets.js new file mode 100644 index 000000000..3955e324b --- /dev/null +++ b/platform/mv3/make-scriptlets.js @@ -0,0 +1,208 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017-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 +*/ + +'use strict'; + +/******************************************************************************/ + +import fs from 'fs/promises'; +import { builtinScriptlets } from './scriptlets.js'; + +/******************************************************************************/ + +const resourceDetails = new Map(); +const resourceAliases = new Map(); +const scriptletFiles = new Map(); + +/******************************************************************************/ + +function createScriptletCoreCode(scriptletToken) { + const details = resourceDetails.get(scriptletToken); + const components = new Map([ [ scriptletToken, details.code ] ]); + const dependencies = details.dependencies && details.dependencies.slice() || []; + while ( dependencies.length !== 0 ) { + const token = dependencies.shift(); + if ( components.has(token) ) { continue; } + const details = resourceDetails.get(token); + if ( details === undefined ) { continue; } + components.set(token, details.code); + if ( Array.isArray(details.dependencies) === false ) { continue; } + dependencies.push(...details.dependencies); + } + return Array.from(components.values()).join('\n\n'); +} + +/******************************************************************************/ + +function safeReplace(text, pattern, replacement, count = 1) { + const rePattern = typeof pattern === 'string' + ? new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + : pattern; + let out = text; + for (;;) { + const match = rePattern.exec(out); + if ( match === null ) { break; } + out = out.slice(0, match.index) + + replacement + + out.slice(match.index + match[0].length); + count -= 1; + if ( count === 0 ) { break; } + } + return out; +} + +/******************************************************************************/ + +export function init() { + for ( const scriptlet of builtinScriptlets ) { + const { name, aliases, fn } = scriptlet; + const entry = { + name: fn.name, + code: fn.toString(), + dependencies: scriptlet.dependencies, + requiresTrust: scriptlet.requiresTrust === true, + }; + resourceDetails.set(name, entry); + if ( Array.isArray(aliases) === false ) { continue; } + for ( const alias of aliases ) { + resourceAliases.set(alias, name); + } + } +} + +/******************************************************************************/ + +export function reset() { + scriptletFiles.clear(); +} + +/******************************************************************************/ + +export function compile(details, isTrusted) { + if ( details.args[0].endsWith('.js') === false ) { + details.args[0] += '.js'; + } + if ( resourceAliases.has(details.args[0]) ) { + details.args[0] = resourceAliases.get(details.args[0]); + } + const scriptletToken = details.args[0]; + const resourceEntry = resourceDetails.get(scriptletToken); + if ( resourceEntry === undefined ) { return; } + if ( resourceEntry.requiresTrust && isTrusted !== true ) { + console.log(`Rejecting ${scriptletToken}: source is not trusted`); + return; + } + if ( scriptletFiles.has(scriptletToken) === false ) { + scriptletFiles.set(scriptletToken, { + name: resourceEntry.name, + code: createScriptletCoreCode(scriptletToken), + args: new Map(), + hostnames: new Map(), + entities: new Map(), + exceptions: new Map(), + matches: new Set(), + }); + } + const scriptletDetails = scriptletFiles.get(scriptletToken); + const argsToken = JSON.stringify(details.args.slice(1)); + if ( scriptletDetails.args.has(argsToken) === false ) { + scriptletDetails.args.set(argsToken, scriptletDetails.args.size); + } + const iArgs = scriptletDetails.args.get(argsToken); + if ( details.matches ) { + for ( const hn of details.matches ) { + if ( hn.endsWith('.*') ) { + scriptletDetails.matches.clear(); + scriptletDetails.matches.add('*'); + const entity = hn.slice(0, -2); + if ( scriptletDetails.entities.has(entity) === false ) { + scriptletDetails.entities.set(entity, new Set()); + } + scriptletDetails.entities.get(entity).add(iArgs); + } else { + if ( scriptletDetails.matches.has('*') === false ) { + scriptletDetails.matches.add(hn); + } + if ( scriptletDetails.hostnames.has(hn) === false ) { + scriptletDetails.hostnames.set(hn, new Set()); + } + scriptletDetails.hostnames.get(hn).add(iArgs); + } + } + } else { + scriptletDetails.matches.add('*'); + } + if ( details.excludeMatches ) { + for ( const hn of details.excludeMatches ) { + if ( scriptletDetails.exceptions.has(hn) === false ) { + scriptletDetails.exceptions.set(hn, []); + } + scriptletDetails.exceptions.get(hn).push(iArgs); + } + } +} + +/******************************************************************************/ + +export async function commit(rulesetId, path, writeFn) { + const scriptletTemplate = await fs.readFile( + './scriptlet.template.js', + { encoding: 'utf8' } + ); + const patchHnMap = hnmap => { + const out = Array.from(hnmap); + out.forEach(a => { + const values = Array.from(a[1]); + a[1] = values.length === 1 ? values[0] : values; + }); + return out; + }; + const scriptletStats = []; + for ( const [ name, details ] of scriptletFiles ) { + let content = safeReplace(scriptletTemplate, + 'function $scriptletName$(){}', + details.code + ); + content = safeReplace(content, /\$rulesetId\$/, rulesetId, 0); + content = safeReplace(content, /\$scriptletName\$/, details.name, 0); + content = safeReplace(content, + 'self.$argsList$', + JSON.stringify(Array.from(details.args.keys())) + ); + content = safeReplace(content, + 'self.$hostnamesMap$', + JSON.stringify(patchHnMap(details.hostnames)) + ); + content = safeReplace(content, + 'self.$entitiesMap$', + JSON.stringify(patchHnMap(details.entities)) + ); + content = safeReplace(content, + 'self.$exceptionsMap$', + JSON.stringify(Array.from(details.exceptions)) + ); + writeFn(`${path}/${rulesetId}.${name}`, content); + scriptletStats.push([ name.slice(0, -3), Array.from(details.matches).sort() ]); + } + return scriptletStats; +} + +/******************************************************************************/ diff --git a/platform/mv3/scriptlet.template.js b/platform/mv3/scriptlet.template.js new file mode 100644 index 000000000..aa3a6e1e0 --- /dev/null +++ b/platform/mv3/scriptlet.template.js @@ -0,0 +1,123 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2014-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'; + +// ruleset: $rulesetId$ + +/******************************************************************************/ + +// Important! +// Isolate from global scope + +(function uBOL_$scriptletName$() { + +/******************************************************************************/ + +const scriptletGlobals = new Map(); // jshint ignore: line + +const argsList = self.$argsList$; + +const hostnamesMap = new Map(self.$hostnamesMap$); + +const entitiesMap = new Map(self.$entitiesMap$); + +const exceptionsMap = new Map(self.$exceptionsMap$); + +/******************************************************************************/ + +function $scriptletName$(){} + +/******************************************************************************/ + +const hnParts = []; +try { hnParts.push(...document.location.hostname.split('.')); } +catch(ex) { } +const hnpartslen = hnParts.length; +if ( hnpartslen === 0 ) { return; } + +const todoIndices = new Set(); +const tonotdoIndices = []; + +// Exceptions +if ( exceptionsMap.size !== 0 ) { + for ( let i = 0; i < hnpartslen; i++ ) { + const hn = hnParts.slice(i).join('.'); + const excepted = exceptionsMap.get(hn); + if ( excepted ) { tonotdoIndices.push(...excepted); } + } + exceptionsMap.clear(); +} + +// Hostname-based +if ( hostnamesMap.size !== 0 ) { + const collectArgIndices = hn => { + let argsIndices = hostnamesMap.get(hn); + if ( argsIndices === undefined ) { return; } + if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } + for ( const argsIndex of argsIndices ) { + if ( tonotdoIndices.includes(argsIndex) ) { continue; } + todoIndices.add(argsIndex); + } + }; + for ( let i = 0; i < hnpartslen; i++ ) { + const hn = hnParts.slice(i).join('.'); + collectArgIndices(hn); + } + collectArgIndices('*'); + hostnamesMap.clear(); +} + +// Entity-based +if ( entitiesMap.size !== 0 ) { + const n = hnpartslen - 1; + for ( let i = 0; i < n; i++ ) { + for ( let j = n; j > i; j-- ) { + const en = hnParts.slice(i,j).join('.'); + let argsIndices = entitiesMap.get(en); + if ( argsIndices === undefined ) { continue; } + if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } + for ( const argsIndex of argsIndices ) { + if ( tonotdoIndices(argsIndex) ) { continue; } + todoIndices.add(argsIndex); + } + } + } + entitiesMap.clear(); +} + +// Apply scriplets +for ( const i of todoIndices ) { + try { $scriptletName$(...JSON.parse(argsList[i])); } + catch(ex) {} +} +argsList.length = 0; + +/******************************************************************************/ + +})(); + +/******************************************************************************/ + +void 0; diff --git a/platform/mv3/scriptlets/abort-current-script.entity.js b/platform/mv3/scriptlets/abort-current-script.entity.js deleted file mode 100644 index 689cf851f..000000000 --- a/platform/mv3/scriptlets/abort-current-script.entity.js +++ /dev/null @@ -1,181 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name abort-current-script.entity -/// alias acs.entity -/// alias abort-current-inline-script.entity -/// alias acis.entity - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_abortCurrentScriptEntity() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const entitiesMap = new Map(self.$entitiesMap$); - -/******************************************************************************/ - -// Issues to mind before changing anything: -// https://github.com/uBlockOrigin/uBlock-issues/issues/2154 - -const scriptlet = ( - target = '', - needle = '', - context = '' -) => { - if ( target === '' ) { return; } - const reRegexEscape = /[.*+?^${}()|[\]\\]/g; - const reNeedle = (( ) => { - if ( needle === '' ) { return /^/; } - if ( /^\/.+\/$/.test(needle) ) { - return new RegExp(needle.slice(1,-1)); - } - return new RegExp(needle.replace(reRegexEscape, '\\$&')); - })(); - const reContext = (( ) => { - if ( context === '' ) { return; } - if ( /^\/.+\/$/.test(context) ) { - return new RegExp(context.slice(1,-1)); - } - return new RegExp(context.replace(reRegexEscape, '\\$&')); - })(); - const chain = target.split('.'); - let owner = window; - let prop; - for (;;) { - prop = chain.shift(); - if ( chain.length === 0 ) { break; } - owner = owner[prop]; - if ( owner instanceof Object === false ) { return; } - } - let value; - let desc = Object.getOwnPropertyDescriptor(owner, prop); - if ( - desc instanceof Object === false || - desc.get instanceof Function === false - ) { - value = owner[prop]; - desc = undefined; - } - const magic = String.fromCharCode(Date.now() % 26 + 97) + - Math.floor(Math.random() * 982451653 + 982451653).toString(36); - const scriptTexts = new WeakMap(); - const getScriptText = elem => { - let text = elem.textContent; - if ( text.trim() !== '' ) { return text; } - if ( scriptTexts.has(elem) ) { return scriptTexts.get(elem); } - const [ , mime, content ] = - /^data:([^,]*),(.+)$/.exec(elem.src.trim()) || - [ '', '', '' ]; - try { - switch ( true ) { - case mime.endsWith(';base64'): - text = self.atob(content); - break; - default: - text = self.decodeURIComponent(content); - break; - } - } catch(ex) { - } - scriptTexts.set(elem, text); - return text; - }; - const validate = ( ) => { - const e = document.currentScript; - if ( e instanceof HTMLScriptElement === false ) { return; } - if ( reContext !== undefined && reContext.test(e.src) === false ) { - return; - } - if ( reNeedle.test(getScriptText(e)) === false ) { return; } - throw new ReferenceError(magic); - }; - Object.defineProperty(owner, prop, { - get: function() { - validate(); - return desc instanceof Object - ? desc.get.call(owner) - : value; - }, - set: function(a) { - validate(); - if ( desc instanceof Object ) { - desc.set.call(owner, a); - } else { - value = a; - } - } - }); - const oe = window.onerror; - window.onerror = function(msg) { - if ( typeof msg === 'string' && msg.includes(magic) ) { - return true; - } - if ( oe instanceof Function ) { - return oe.apply(this, arguments); - } - }.bind(); -}; - -/******************************************************************************/ - -const hnparts = []; -try { hnparts.push(...document.location.hostname.split('.')); } catch(ex) { } -const hnpartslen = hnparts.length - 1; -for ( let i = 0; i < hnpartslen; i++ ) { - for ( let j = hnpartslen; j > i; j-- ) { - const hn = hnparts.slice(i).join('.'); - const en = hnparts.slice(i,j).join('.'); - let argsIndices = entitiesMap.get(en); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } -} - -argsList.length = 0; -entitiesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/scriptlets/abort-current-script.js b/platform/mv3/scriptlets/abort-current-script.js deleted file mode 100644 index d7ae23ff7..000000000 --- a/platform/mv3/scriptlets/abort-current-script.js +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name abort-current-script -/// alias acs -/// alias abort-current-inline-script -/// alias acis - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_abortCurrentScript() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -// Issues to mind before changing anything: -// https://github.com/uBlockOrigin/uBlock-issues/issues/2154 - -const scriptlet = ( - target = '', - needle = '', - context = '' -) => { - if ( target === '' ) { return; } - const reRegexEscape = /[.*+?^${}()|[\]\\]/g; - const reNeedle = (( ) => { - if ( needle === '' ) { return /^/; } - if ( /^\/.+\/$/.test(needle) ) { - return new RegExp(needle.slice(1,-1)); - } - return new RegExp(needle.replace(reRegexEscape, '\\$&')); - })(); - const reContext = (( ) => { - if ( context === '' ) { return; } - if ( /^\/.+\/$/.test(context) ) { - return new RegExp(context.slice(1,-1)); - } - return new RegExp(context.replace(reRegexEscape, '\\$&')); - })(); - const chain = target.split('.'); - let owner = window; - let prop; - for (;;) { - prop = chain.shift(); - if ( chain.length === 0 ) { break; } - owner = owner[prop]; - if ( owner instanceof Object === false ) { return; } - } - let value; - let desc = Object.getOwnPropertyDescriptor(owner, prop); - if ( - desc instanceof Object === false || - desc.get instanceof Function === false - ) { - value = owner[prop]; - desc = undefined; - } - const magic = String.fromCharCode(Date.now() % 26 + 97) + - Math.floor(Math.random() * 982451653 + 982451653).toString(36); - const scriptTexts = new WeakMap(); - const getScriptText = elem => { - let text = elem.textContent; - if ( text.trim() !== '' ) { return text; } - if ( scriptTexts.has(elem) ) { return scriptTexts.get(elem); } - const [ , mime, content ] = - /^data:([^,]*),(.+)$/.exec(elem.src.trim()) || - [ '', '', '' ]; - try { - switch ( true ) { - case mime.endsWith(';base64'): - text = self.atob(content); - break; - default: - text = self.decodeURIComponent(content); - break; - } - } catch(ex) { - } - scriptTexts.set(elem, text); - return text; - }; - const validate = ( ) => { - const e = document.currentScript; - if ( e instanceof HTMLScriptElement === false ) { return; } - if ( reContext !== undefined && reContext.test(e.src) === false ) { - return; - } - if ( reNeedle.test(getScriptText(e)) === false ) { return; } - throw new ReferenceError(magic); - }; - Object.defineProperty(owner, prop, { - get: function() { - validate(); - return desc instanceof Object - ? desc.get.call(owner) - : value; - }, - set: function(a) { - validate(); - if ( desc instanceof Object ) { - desc.set.call(owner, a); - } else { - value = a; - } - } - }); - const oe = window.onerror; - window.onerror = function(msg) { - if ( typeof msg === 'string' && msg.includes(magic) ) { - return true; - } - if ( oe instanceof Function ) { - return oe.apply(this, arguments); - } - }.bind(); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/scriptlets/abort-on-property-read.entity.js b/platform/mv3/scriptlets/abort-on-property-read.entity.js deleted file mode 100644 index 499c2281c..000000000 --- a/platform/mv3/scriptlets/abort-on-property-read.entity.js +++ /dev/null @@ -1,139 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name abort-on-property-read.entity -/// alias aopr.entity - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_abortOnPropertyReadEntity() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const entitiesMap = new Map(self.$entitiesMap$); - -/******************************************************************************/ - -const ObjGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; -const ObjDefineProperty = Object.defineProperty; - -const magic = - String.fromCharCode(Date.now() % 26 + 97) + - Math.floor(Math.random() * 982451653 + 982451653).toString(36); - -const abort = function() { - throw new ReferenceError(magic); -}; - -const makeProxy = function(owner, chain) { - const pos = chain.indexOf('.'); - if ( pos === -1 ) { - const desc = ObjGetOwnPropertyDescriptor(owner, chain); - if ( !desc || desc.get !== abort ) { - ObjDefineProperty(owner, chain, { - get: abort, - set: function(){} - }); - } - return; - } - - const prop = chain.slice(0, pos); - let v = owner[prop]; - chain = chain.slice(pos + 1); - if ( v ) { - makeProxy(v, chain); - return; - } - - const desc = ObjGetOwnPropertyDescriptor(owner, prop); - if ( desc && desc.set !== undefined ) { return; } - - ObjDefineProperty(owner, prop, { - get: function() { return v; }, - set: function(a) { - v = a; - if ( a instanceof Object ) { - makeProxy(a, chain); - } - } - }); -}; - -const scriptlet = ( - chain = '' -) => { - const owner = window; - makeProxy(owner, chain); - const oe = window.onerror; - window.onerror = function(msg, src, line, col, error) { - if ( typeof msg === 'string' && msg.includes(magic) ) { - return true; - } - if ( oe instanceof Function ) { - return oe(msg, src, line, col, error); - } - }.bind(); -}; - -/******************************************************************************/ - -const hnparts = []; -try { hnparts.push(...document.location.hostname.split('.')); } catch(ex) { } -const hnpartslen = hnparts.length - 1; -for ( let i = 0; i < hnpartslen; i++ ) { - for ( let j = hnpartslen; j > i; j-- ) { - const hn = hnparts.slice(i).join('.'); - const en = hnparts.slice(i,j).join('.'); - let argsIndices = entitiesMap.get(en); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } -} - -argsList.length = 0; -entitiesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/scriptlets/abort-on-property-read.js b/platform/mv3/scriptlets/abort-on-property-read.js deleted file mode 100644 index 5a8092288..000000000 --- a/platform/mv3/scriptlets/abort-on-property-read.js +++ /dev/null @@ -1,143 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name abort-on-property-read -/// alias aopr - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_abortOnPropertyRead() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -const ObjGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; -const ObjDefineProperty = Object.defineProperty; - -const magic = - String.fromCharCode(Date.now() % 26 + 97) + - Math.floor(Math.random() * 982451653 + 982451653).toString(36); - -const abort = function() { - throw new ReferenceError(magic); -}; - -const makeProxy = function(owner, chain) { - const pos = chain.indexOf('.'); - if ( pos === -1 ) { - const desc = ObjGetOwnPropertyDescriptor(owner, chain); - if ( !desc || desc.get !== abort ) { - ObjDefineProperty(owner, chain, { - get: abort, - set: function(){} - }); - } - return; - } - - const prop = chain.slice(0, pos); - let v = owner[prop]; - chain = chain.slice(pos + 1); - if ( v ) { - makeProxy(v, chain); - return; - } - - const desc = ObjGetOwnPropertyDescriptor(owner, prop); - if ( desc && desc.set !== undefined ) { return; } - - ObjDefineProperty(owner, prop, { - get: function() { return v; }, - set: function(a) { - v = a; - if ( a instanceof Object ) { - makeProxy(a, chain); - } - } - }); -}; - -const scriptlet = ( - chain = '' -) => { - const owner = window; - makeProxy(owner, chain); - const oe = window.onerror; - window.onerror = function(msg, src, line, col, error) { - if ( typeof msg === 'string' && msg.includes(magic) ) { - return true; - } - if ( oe instanceof Function ) { - return oe(msg, src, line, col, error); - } - }.bind(); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ - diff --git a/platform/mv3/scriptlets/abort-on-property-write.entity.js b/platform/mv3/scriptlets/abort-on-property-write.entity.js deleted file mode 100644 index fa0ebe260..000000000 --- a/platform/mv3/scriptlets/abort-on-property-write.entity.js +++ /dev/null @@ -1,113 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name abort-on-property-write.entity -/// alias aopw.entity - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_abortOnPropertyWriteEntity() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const entitiesMap = new Map(self.$entitiesMap$); - -/******************************************************************************/ - -const magic = - String.fromCharCode(Date.now() % 26 + 97) + - Math.floor(Math.random() * 982451653 + 982451653).toString(36); - -const abort = function() { - throw new ReferenceError(magic); -}; - -const scriptlet = ( - prop = '' -) => { - let owner = window; - for (;;) { - const pos = prop.indexOf('.'); - if ( pos === -1 ) { break; } - owner = owner[prop.slice(0, pos)]; - if ( owner instanceof Object === false ) { return; } - prop = prop.slice(pos + 1); - } - delete owner[prop]; - Object.defineProperty(owner, prop, { - set: function() { - abort(); - } - }); - const oe = window.onerror; - window.onerror = function(msg, src, line, col, error) { - if ( typeof msg === 'string' && msg.includes(magic) ) { - return true; - } - if ( oe instanceof Function ) { - return oe(msg, src, line, col, error); - } - }.bind(); -}; - -/******************************************************************************/ - -const hnparts = []; -try { hnparts.push(...document.location.hostname.split('.')); } catch(ex) { } -const hnpartslen = hnparts.length - 1; -for ( let i = 0; i < hnpartslen; i++ ) { - for ( let j = hnpartslen; j > i; j-- ) { - const hn = hnparts.slice(i).join('.'); - const en = hnparts.slice(i,j).join('.'); - let argsIndices = entitiesMap.get(en); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } -} - -argsList.length = 0; -entitiesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/scriptlets/abort-on-property-write.js b/platform/mv3/scriptlets/abort-on-property-write.js deleted file mode 100644 index 6003a4b26..000000000 --- a/platform/mv3/scriptlets/abort-on-property-write.js +++ /dev/null @@ -1,117 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name abort-on-property-write -/// alias aopw - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_abortOnPropertyWrite() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -const magic = - String.fromCharCode(Date.now() % 26 + 97) + - Math.floor(Math.random() * 982451653 + 982451653).toString(36); - -const abort = function() { - throw new ReferenceError(magic); -}; - -const scriptlet = ( - prop = '' -) => { - let owner = window; - for (;;) { - const pos = prop.indexOf('.'); - if ( pos === -1 ) { break; } - owner = owner[prop.slice(0, pos)]; - if ( owner instanceof Object === false ) { return; } - prop = prop.slice(pos + 1); - } - delete owner[prop]; - Object.defineProperty(owner, prop, { - set: function() { - abort(); - } - }); - const oe = window.onerror; - window.onerror = function(msg, src, line, col, error) { - if ( typeof msg === 'string' && msg.includes(magic) ) { - return true; - } - if ( oe instanceof Function ) { - return oe(msg, src, line, col, error); - } - }.bind(); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ - diff --git a/platform/mv3/scriptlets/json-prune.js b/platform/mv3/scriptlets/json-prune.js deleted file mode 100644 index 253da2376..000000000 --- a/platform/mv3/scriptlets/json-prune.js +++ /dev/null @@ -1,160 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name json-prune - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_jsonPrune() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -// https://github.com/uBlockOrigin/uBlock-issues/issues/1545 -// - Add support for "remove everything if needle matches" case - -const scriptlet = ( - rawPrunePaths = '', - rawNeedlePaths = '' -) => { - const prunePaths = rawPrunePaths !== '' - ? rawPrunePaths.split(/ +/) - : []; - let needlePaths; - if ( prunePaths.length === 0 ) { return; } - needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== '' - ? rawNeedlePaths.split(/ +/) - : []; - const findOwner = function(root, path, prune = false) { - let owner = root; - let chain = path; - for (;;) { - if ( typeof owner !== 'object' || owner === null ) { - return false; - } - const pos = chain.indexOf('.'); - if ( pos === -1 ) { - if ( prune === false ) { - return owner.hasOwnProperty(chain); - } - if ( chain === '*' ) { - for ( const key in owner ) { - if ( owner.hasOwnProperty(key) === false ) { continue; } - delete owner[key]; - } - } else if ( owner.hasOwnProperty(chain) ) { - delete owner[chain]; - } - return true; - } - const prop = chain.slice(0, pos); - if ( - prop === '[]' && Array.isArray(owner) || - prop === '*' && owner instanceof Object - ) { - const next = chain.slice(pos + 1); - let found = false; - for ( const key of Object.keys(owner) ) { - found = findOwner(owner[key], next, prune) || found; - } - return found; - } - if ( owner.hasOwnProperty(prop) === false ) { return false; } - owner = owner[prop]; - chain = chain.slice(pos + 1); - } - }; - const mustProcess = function(root) { - for ( const needlePath of needlePaths ) { - if ( findOwner(root, needlePath) === false ) { - return false; - } - } - return true; - }; - const pruner = function(o) { - if ( mustProcess(o) === false ) { return o; } - for ( const path of prunePaths ) { - findOwner(o, path, true); - } - return o; - }; - JSON.parse = new Proxy(JSON.parse, { - apply: function() { - return pruner(Reflect.apply(...arguments)); - }, - }); - Response.prototype.json = new Proxy(Response.prototype.json, { - apply: function() { - return Reflect.apply(...arguments).then(o => pruner(o)); - }, - }); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/scriptlets/no-addeventlistener-if.entity.js b/platform/mv3/scriptlets/no-addeventlistener-if.entity.js deleted file mode 100644 index dac815011..000000000 --- a/platform/mv3/scriptlets/no-addeventlistener-if.entity.js +++ /dev/null @@ -1,175 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name no-addeventlistener-if.entity -/// alias noaelif.entity -/// alias aeld.entity - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_noAddEventListenerIfEntity() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const entitiesMap = new Map(self.$entitiesMap$); - -/******************************************************************************/ - -const regexpFromArg = arg => { - if ( arg === '' ) { return /^/; } - if ( /^\/.+\/$/.test(arg) ) { return new RegExp(arg.slice(1,-1)); } - return new RegExp(arg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); -}; - -/******************************************************************************/ - -// Dependencies - -const scriptletGlobals = new Map(); - -function safeSelf() { - if ( scriptletGlobals.has('safeSelf') ) { - return scriptletGlobals.get('safeSelf'); - } - const safe = { - 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, - 'RegExp_exec': self.RegExp.prototype.exec, - 'addEventListener': self.EventTarget.prototype.addEventListener, - 'removeEventListener': self.EventTarget.prototype.removeEventListener, - }; - scriptletGlobals.set('safeSelf', safe); - return safe; -} - -function runAt(fn, when) { - const intFromReadyState = state => { - const targets = { - 'loading': 1, - 'interactive': 2, 'end': 2, '2': 2, - 'complete': 3, 'idle': 3, '3': 3, - }; - const tokens = Array.isArray(state) ? state : [ state ]; - for ( const token of tokens ) { - const prop = `${token}`; - if ( targets.hasOwnProperty(prop) === false ) { continue; } - return targets[prop]; - } - return 0; - }; - const runAt = intFromReadyState(when); - if ( intFromReadyState(document.readyState) >= runAt ) { - fn(); return; - } - const onStateChange = ( ) => { - if ( intFromReadyState(document.readyState) < runAt ) { return; } - fn(); - safe.removeEventListener.apply(document, args); - }; - const safe = safeSelf(); - const args = [ 'readystatechange', onStateChange, { capture: true } ]; - safe.addEventListener.apply(document, args); -} - -/******************************************************************************/ - -const scriptlet = ( - arg1 = '', - arg2 = '' -) => { - const details = typeof arg1 !== 'object' - ? { type: arg1, pattern: arg2 } - : arg1; - const { type = '', pattern = '' } = details; - if ( typeof type !== 'string' ) { return; } - if ( typeof pattern !== 'string' ) { return; } - const reType = regexpFromArg(type); - const rePattern = regexpFromArg(pattern); - const trapEddEventListeners = ( ) => { - const eventListenerHandler = { - apply: function(target, thisArg, args) { - let type, handler; - try { - type = String(args[0]); - handler = String(args[1]); - } catch(ex) { - } - if ( - reType.test(type) === false || - rePattern.test(handler) === false - ) { - return target.apply(thisArg, args); - } - } - }; - self.EventTarget.prototype.addEventListener = new Proxy( - self.EventTarget.prototype.addEventListener, - eventListenerHandler - ); - }; - runAt(( ) => { - trapEddEventListeners(); - }, details.runAt); -}; - -/******************************************************************************/ - -const hnparts = []; -try { hnparts.push(...document.location.hostname.split('.')); } catch(ex) { } -const hnpartslen = hnparts.length - 1; -for ( let i = 0; i < hnpartslen; i++ ) { - for ( let j = hnpartslen; j > i; j-- ) { - const hn = hnparts.slice(i).join('.'); - const en = hnparts.slice(i,j).join('.'); - let argsIndices = entitiesMap.get(en); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } -} - -argsList.length = 0; -entitiesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/scriptlets/no-addeventlistener-if.js b/platform/mv3/scriptlets/no-addeventlistener-if.js deleted file mode 100644 index 7ec69df79..000000000 --- a/platform/mv3/scriptlets/no-addeventlistener-if.js +++ /dev/null @@ -1,180 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name no-addeventlistener-if -/// alias noaelif -/// alias addEventListener-defuser -/// alias aeld - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_noAddEventListenerIf() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -const regexpFromArg = arg => { - if ( arg === '' ) { return /^/; } - if ( /^\/.+\/$/.test(arg) ) { return new RegExp(arg.slice(1,-1)); } - return new RegExp(arg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); -}; - -/******************************************************************************/ - -// Dependencies - -const scriptletGlobals = new Map(); - -function safeSelf() { - if ( scriptletGlobals.has('safeSelf') ) { - return scriptletGlobals.get('safeSelf'); - } - const safe = { - 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, - 'RegExp_exec': self.RegExp.prototype.exec, - 'addEventListener': self.EventTarget.prototype.addEventListener, - 'removeEventListener': self.EventTarget.prototype.removeEventListener, - }; - scriptletGlobals.set('safeSelf', safe); - return safe; -} - -function runAt(fn, when) { - const intFromReadyState = state => { - const targets = { - 'loading': 1, - 'interactive': 2, 'end': 2, '2': 2, - 'complete': 3, 'idle': 3, '3': 3, - }; - const tokens = Array.isArray(state) ? state : [ state ]; - for ( const token of tokens ) { - const prop = `${token}`; - if ( targets.hasOwnProperty(prop) === false ) { continue; } - return targets[prop]; - } - return 0; - }; - const runAt = intFromReadyState(when); - if ( intFromReadyState(document.readyState) >= runAt ) { - fn(); return; - } - const onStateChange = ( ) => { - if ( intFromReadyState(document.readyState) < runAt ) { return; } - fn(); - safe.removeEventListener.apply(document, args); - }; - const safe = safeSelf(); - const args = [ 'readystatechange', onStateChange, { capture: true } ]; - safe.addEventListener.apply(document, args); -} - -/******************************************************************************/ - -const scriptlet = ( - arg1 = '', - arg2 = '' -) => { - const details = typeof arg1 !== 'object' - ? { type: arg1, pattern: arg2 } - : arg1; - const { type = '', pattern = '' } = details; - if ( typeof type !== 'string' ) { return; } - if ( typeof pattern !== 'string' ) { return; } - const reType = regexpFromArg(type); - const rePattern = regexpFromArg(pattern); - const trapEddEventListeners = ( ) => { - const eventListenerHandler = { - apply: function(target, thisArg, args) { - let type, handler; - try { - type = String(args[0]); - handler = String(args[1]); - } catch(ex) { - } - if ( - reType.test(type) === false || - rePattern.test(handler) === false - ) { - return target.apply(thisArg, args); - } - } - }; - self.EventTarget.prototype.addEventListener = new Proxy( - self.EventTarget.prototype.addEventListener, - eventListenerHandler - ); - }; - runAt(( ) => { - trapEddEventListeners(); - }, details.runAt); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ - diff --git a/platform/mv3/scriptlets/no-fetch-if.js b/platform/mv3/scriptlets/no-fetch-if.js deleted file mode 100644 index f7b8915c0..000000000 --- a/platform/mv3/scriptlets/no-fetch-if.js +++ /dev/null @@ -1,143 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name no-fetch-if - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_noFetchIf() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -const scriptlet = ( - conditions = '' -) => { - const needles = []; - for ( const condition of conditions.split(/\s+/) ) { - if ( condition === '' ) { continue; } - const pos = condition.indexOf(':'); - let key, value; - if ( pos !== -1 ) { - key = condition.slice(0, pos); - value = condition.slice(pos + 1); - } else { - key = 'url'; - value = condition; - } - if ( value === '' ) { - value = '^'; - } else if ( value.startsWith('/') && value.endsWith('/') ) { - value = value.slice(1, -1); - } else { - value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } - needles.push({ key, re: new RegExp(value) }); - } - self.fetch = new Proxy(self.fetch, { - apply: function(target, thisArg, args) { - let proceed = true; - try { - let details; - if ( args[0] instanceof self.Request ) { - details = args[0]; - } else { - details = Object.assign({ url: args[0] }, args[1]); - } - const props = new Map(); - for ( const prop in details ) { - let v = details[prop]; - if ( typeof v !== 'string' ) { - try { v = JSON.stringify(v); } - catch(ex) { } - } - if ( typeof v !== 'string' ) { continue; } - props.set(prop, v); - } - proceed = needles.length === 0; - for ( const { key, re } of needles ) { - if ( - props.has(key) === false || - re.test(props.get(key)) === false - ) { - proceed = true; - break; - } - } - } catch(ex) { - } - return proceed - ? Reflect.apply(target, thisArg, args) - : Promise.resolve(new Response()); - } - }); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ - diff --git a/platform/mv3/scriptlets/no-setinterval-if.js b/platform/mv3/scriptlets/no-setinterval-if.js deleted file mode 100644 index 77f4cab24..000000000 --- a/platform/mv3/scriptlets/no-setinterval-if.js +++ /dev/null @@ -1,127 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name no-setinterval-if -/// alias no-setInterval-if -/// alias nosiif - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_noSetIntervalIf() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -const scriptlet = ( - needle = '', - delay = '' -) => { - const needleNot = needle.charAt(0) === '!'; - if ( needleNot ) { needle = needle.slice(1); } - if ( delay === '' ) { delay = undefined; } - let delayNot = false; - if ( delay !== undefined ) { - delayNot = delay.charAt(0) === '!'; - if ( delayNot ) { delay = delay.slice(1); } - delay = parseInt(delay, 10); - } - if ( needle.startsWith('/') && needle.endsWith('/') ) { - needle = needle.slice(1,-1); - } else if ( needle !== '' ) { - needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } - const reNeedle = new RegExp(needle); - const regexpTest = RegExp.prototype.test; - self.setInterval = new Proxy(self.setInterval, { - apply: function(target, thisArg, args) { - const a = String(args[0]); - const b = args[1]; - let defuse; - if ( needle !== '' ) { - defuse = regexpTest.call(reNeedle, a) !== needleNot; - } - if ( defuse !== false && delay !== undefined ) { - defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; - } - if ( defuse ) { - args[0] = function(){}; - } - return target.apply(thisArg, args); - }, - get(target, prop, receiver) { - if ( prop === 'toString' ) { - return target.toString.bind(target); - } - return Reflect.get(target, prop, receiver); - }, - }); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ - diff --git a/platform/mv3/scriptlets/no-settimeout-if.entity.js b/platform/mv3/scriptlets/no-settimeout-if.entity.js deleted file mode 100644 index 1f5f07031..000000000 --- a/platform/mv3/scriptlets/no-settimeout-if.entity.js +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name no-settimeout-if.entity -/// alias no-setTimeout-if.entity -/// alias nostif.entity - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_noSetTimeoutIfEntity() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const entitiesMap = new Map(self.$entitiesMap$); - -/******************************************************************************/ - -const scriptlet = ( - needle = '', - delay = '' -) => { - const needleNot = needle.charAt(0) === '!'; - if ( needleNot ) { needle = needle.slice(1); } - if ( delay === '' ) { delay = undefined; } - let delayNot = false; - if ( delay !== undefined ) { - delayNot = delay.charAt(0) === '!'; - if ( delayNot ) { delay = delay.slice(1); } - delay = parseInt(delay, 10); - } - if ( needle.startsWith('/') && needle.endsWith('/') ) { - needle = needle.slice(1,-1); - } else if ( needle !== '' ) { - needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } - const reNeedle = new RegExp(needle); - const regexpTest = RegExp.prototype.test; - self.setTimeout = new Proxy(self.setTimeout, { - apply: function(target, thisArg, args) { - const a = String(args[0]); - const b = args[1]; - let defuse; - if ( needle !== '' ) { - defuse = regexpTest.call(reNeedle, a) !== needleNot; - } - if ( defuse !== false && delay !== undefined ) { - defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; - } - if ( defuse ) { - args[0] = function(){}; - } - return target.apply(thisArg, args); - }, - get(target, prop, receiver) { - if ( prop === 'toString' ) { - return target.toString.bind(target); - } - return Reflect.get(target, prop, receiver); - }, - }); -}; - -/******************************************************************************/ - -const hnparts = []; -try { hnparts.push(...document.location.hostname.split('.')); } catch(ex) { } -const hnpartslen = hnparts.length - 1; -for ( let i = 0; i < hnpartslen; i++ ) { - for ( let j = hnpartslen; j > i; j-- ) { - const hn = hnparts.slice(i).join('.'); - const en = hnparts.slice(i,j).join('.'); - let argsIndices = entitiesMap.get(en); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } -} - -argsList.length = 0; -entitiesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/scriptlets/no-settimeout-if.js b/platform/mv3/scriptlets/no-settimeout-if.js deleted file mode 100644 index c2d80b6a3..000000000 --- a/platform/mv3/scriptlets/no-settimeout-if.js +++ /dev/null @@ -1,127 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name no-settimeout-if -/// alias no-setTimeout-if -/// alias nostif - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_noSetTimeoutIf() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -const scriptlet = ( - needle = '', - delay = '' -) => { - const needleNot = needle.charAt(0) === '!'; - if ( needleNot ) { needle = needle.slice(1); } - if ( delay === '' ) { delay = undefined; } - let delayNot = false; - if ( delay !== undefined ) { - delayNot = delay.charAt(0) === '!'; - if ( delayNot ) { delay = delay.slice(1); } - delay = parseInt(delay, 10); - } - if ( needle.startsWith('/') && needle.endsWith('/') ) { - needle = needle.slice(1,-1); - } else if ( needle !== '' ) { - needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } - const reNeedle = new RegExp(needle); - const regexpTest = RegExp.prototype.test; - self.setTimeout = new Proxy(self.setTimeout, { - apply: function(target, thisArg, args) { - const a = String(args[0]); - const b = args[1]; - let defuse; - if ( needle !== '' ) { - defuse = regexpTest.call(reNeedle, a) !== needleNot; - } - if ( defuse !== false && delay !== undefined ) { - defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; - } - if ( defuse ) { - args[0] = function(){}; - } - return target.apply(thisArg, args); - }, - get(target, prop, receiver) { - if ( prop === 'toString' ) { - return target.toString.bind(target); - } - return Reflect.get(target, prop, receiver); - }, - }); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ - diff --git a/platform/mv3/scriptlets/no-windowopen-if.entity.js b/platform/mv3/scriptlets/no-windowopen-if.entity.js deleted file mode 100644 index 8ada06c16..000000000 --- a/platform/mv3/scriptlets/no-windowopen-if.entity.js +++ /dev/null @@ -1,154 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name no-windowopen-if.entity -/// alias no-windowOpen-if.entity -/// alias nowoif.entity - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_noWindowOpenIfEntity() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const entitiesMap = new Map(self.$entitiesMap$); - -/******************************************************************************/ - -const scriptlet = ( - needle = '', - delay = '', - options = '' -) => { - const newSyntax = /^[01]?$/.test(needle) === false; - let pattern = ''; - let targetResult = true; - let autoRemoveAfter = -1; - if ( newSyntax ) { - pattern = needle; - if ( pattern.startsWith('!') ) { - targetResult = false; - pattern = pattern.slice(1); - } - autoRemoveAfter = parseInt(delay); - if ( isNaN(autoRemoveAfter) ) { - autoRemoveAfter = -1; - } - } else { - pattern = delay; - if ( needle === '0' ) { - targetResult = false; - } - } - if ( pattern === '' ) { - pattern = '.?'; - } else if ( /^\/.+\/$/.test(pattern) ) { - pattern = pattern.slice(1,-1); - } else { - pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } - const rePattern = new RegExp(pattern); - const createDecoy = function(tag, urlProp, url) { - const decoy = document.createElement(tag); - decoy[urlProp] = url; - decoy.style.setProperty('height','1px', 'important'); - decoy.style.setProperty('position','fixed', 'important'); - decoy.style.setProperty('top','-1px', 'important'); - decoy.style.setProperty('width','1px', 'important'); - document.body.appendChild(decoy); - setTimeout(( ) => decoy.remove(), autoRemoveAfter * 1000); - return decoy; - }; - window.open = new Proxy(window.open, { - apply: function(target, thisArg, args) { - const url = args[0]; - if ( rePattern.test(url) !== targetResult ) { - return target.apply(thisArg, args); - } - if ( autoRemoveAfter < 0 ) { return null; } - const decoy = /\bobj\b/.test(options) - ? createDecoy('object', 'data', url) - : createDecoy('iframe', 'src', url); - let popup = decoy.contentWindow; - if ( typeof popup === 'object' && popup !== null ) { - Object.defineProperty(popup, 'closed', { value: false }); - } else { - const noopFunc = (function(){}).bind(self); - popup = new Proxy(self, { - get: function(target, prop) { - if ( prop === 'closed' ) { return false; } - const r = Reflect.get(...arguments); - if ( typeof r === 'function' ) { return noopFunc; } - return target[prop]; - }, - set: function() { - return Reflect.set(...arguments); - }, - }); - } - return popup; - } - }); -}; - -/******************************************************************************/ - -const hnparts = []; -try { hnparts.push(...document.location.hostname.split('.')); } catch(ex) { } -const hnpartslen = hnparts.length - 1; -for ( let i = 0; i < hnpartslen; i++ ) { - for ( let j = hnpartslen; j > i; j-- ) { - const hn = hnparts.slice(i).join('.'); - const en = hnparts.slice(i,j).join('.'); - let argsIndices = entitiesMap.get(en); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } -} - -argsList.length = 0; -entitiesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/scriptlets/no-windowopen-if.js b/platform/mv3/scriptlets/no-windowopen-if.js deleted file mode 100644 index 53bb879bc..000000000 --- a/platform/mv3/scriptlets/no-windowopen-if.js +++ /dev/null @@ -1,159 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name no-windowopen-if -/// alias no-windowOpen-if -/// alias nowoif -/// alias window.open-defuser - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_noWindowOpenIf() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -const scriptlet = ( - needle = '', - delay = '', - options = '' -) => { - const newSyntax = /^[01]?$/.test(needle) === false; - let pattern = ''; - let targetResult = true; - let autoRemoveAfter = -1; - if ( newSyntax ) { - pattern = needle; - if ( pattern.startsWith('!') ) { - targetResult = false; - pattern = pattern.slice(1); - } - autoRemoveAfter = parseInt(delay); - if ( isNaN(autoRemoveAfter) ) { - autoRemoveAfter = -1; - } - } else { - pattern = delay; - if ( needle === '0' ) { - targetResult = false; - } - } - if ( pattern === '' ) { - pattern = '.?'; - } else if ( /^\/.+\/$/.test(pattern) ) { - pattern = pattern.slice(1,-1); - } else { - pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } - const rePattern = new RegExp(pattern); - const createDecoy = function(tag, urlProp, url) { - const decoy = document.createElement(tag); - decoy[urlProp] = url; - decoy.style.setProperty('height','1px', 'important'); - decoy.style.setProperty('position','fixed', 'important'); - decoy.style.setProperty('top','-1px', 'important'); - decoy.style.setProperty('width','1px', 'important'); - document.body.appendChild(decoy); - setTimeout(( ) => decoy.remove(), autoRemoveAfter * 1000); - return decoy; - }; - window.open = new Proxy(window.open, { - apply: function(target, thisArg, args) { - const url = args[0]; - if ( rePattern.test(url) !== targetResult ) { - return target.apply(thisArg, args); - } - if ( autoRemoveAfter < 0 ) { return null; } - const decoy = /\bobj\b/.test(options) - ? createDecoy('object', 'data', url) - : createDecoy('iframe', 'src', url); - let popup = decoy.contentWindow; - if ( typeof popup === 'object' && popup !== null ) { - Object.defineProperty(popup, 'closed', { value: false }); - } else { - const noopFunc = (function(){}).bind(self); - popup = new Proxy(self, { - get: function(target, prop) { - if ( prop === 'closed' ) { return false; } - const r = Reflect.get(...arguments); - if ( typeof r === 'function' ) { return noopFunc; } - return target[prop]; - }, - set: function() { - return Reflect.set(...arguments); - }, - }); - } - return popup; - } - }); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ - diff --git a/platform/mv3/scriptlets/no-xhr-if.js b/platform/mv3/scriptlets/no-xhr-if.js deleted file mode 100644 index a2cc895d2..000000000 --- a/platform/mv3/scriptlets/no-xhr-if.js +++ /dev/null @@ -1,145 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name no-xhr-if - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_noXhrIf() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -const scriptlet = ( - conditions = '' -) => { - const xhrInstances = new WeakMap(); - const needles = []; - for ( const condition of conditions.split(/\s+/) ) { - if ( condition === '' ) { continue; } - const pos = condition.indexOf(':'); - let key, value; - if ( pos !== -1 ) { - key = condition.slice(0, pos); - value = condition.slice(pos + 1); - } else { - key = 'url'; - value = condition; - } - if ( value === '' ) { - value = '^'; - } else if ( value.startsWith('/') && value.endsWith('/') ) { - value = value.slice(1, -1); - } else { - value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } - needles.push({ key, re: new RegExp(value) }); - } - self.XMLHttpRequest = class extends self.XMLHttpRequest { - open(...args) { - const argNames = [ 'method', 'url' ]; - const haystack = new Map(); - for ( let i = 0; i < args.length && i < argNames.length; i++ ) { - haystack.set(argNames[i], args[i]); - } - if ( haystack.size !== 0 ) { - let matches = true; - for ( const { key, re } of needles ) { - matches = re.test(haystack.get(key) || ''); - if ( matches === false ) { break; } - } - if ( matches ) { - xhrInstances.set(this, haystack); - } - } - return super.open(...args); - } - send(...args) { - const haystack = xhrInstances.get(this); - if ( haystack === undefined ) { - return super.send(...args); - } - Object.defineProperties(this, { - readyState: { value: 4, writable: false }, - response: { value: '', writable: false }, - responseText: { value: '', writable: false }, - responseURL: { value: haystack.get('url'), writable: false }, - responseXML: { value: '', writable: false }, - status: { value: 200, writable: false }, - statusText: { value: 'OK', writable: false }, - }); - this.dispatchEvent(new Event('readystatechange')); - this.dispatchEvent(new Event('load')); - this.dispatchEvent(new Event('loadend')); - } - }; -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ - diff --git a/platform/mv3/scriptlets/set-constant.entity.js b/platform/mv3/scriptlets/set-constant.entity.js deleted file mode 100644 index dacb05bbe..000000000 --- a/platform/mv3/scriptlets/set-constant.entity.js +++ /dev/null @@ -1,313 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name set-constant.entity -/// alias set.entity - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_setConstantEntity() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const entitiesMap = new Map(self.$entitiesMap$); - -/******************************************************************************/ - -// Dependencies - -const scriptletGlobals = new Map(); - -function safeSelf() { - if ( scriptletGlobals.has('safeSelf') ) { - return scriptletGlobals.get('safeSelf'); - } - const safe = { - 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, - 'RegExp_exec': self.RegExp.prototype.exec, - 'addEventListener': self.EventTarget.prototype.addEventListener, - 'removeEventListener': self.EventTarget.prototype.removeEventListener, - }; - scriptletGlobals.set('safeSelf', safe); - return safe; -} - -function runAt(fn, when) { - const intFromReadyState = state => { - const targets = { - 'loading': 1, - 'interactive': 2, 'end': 2, '2': 2, - 'complete': 3, 'idle': 3, '3': 3, - }; - const tokens = Array.isArray(state) ? state : [ state ]; - for ( const token of tokens ) { - const prop = `${token}`; - if ( targets.hasOwnProperty(prop) === false ) { continue; } - return targets[prop]; - } - return 0; - }; - const runAt = intFromReadyState(when); - if ( intFromReadyState(document.readyState) >= runAt ) { - fn(); return; - } - const onStateChange = ( ) => { - if ( intFromReadyState(document.readyState) < runAt ) { return; } - fn(); - safe.removeEventListener.apply(document, args); - }; - const safe = safeSelf(); - const args = [ 'readystatechange', onStateChange, { capture: true } ]; - safe.addEventListener.apply(document, args); -} - -/******************************************************************************/ - -const scriptlet = ( - arg1 = '', - arg2 = '', - arg3 = '' -) => { - const details = typeof arg1 !== 'object' - ? { prop: arg1, value: arg2 } - : arg1; - if ( arg3 !== '' ) { - if ( /^\d$/.test(arg3) ) { - details.options = [ arg3 ]; - } else { - details.options = Array.from(arguments).slice(2); - } - } - const { prop: chain = '', value: cValue = '' } = details; - if ( typeof chain !== 'string' ) { return; } - if ( chain === '' ) { return; } - const options = details.options || []; - function setConstant(chain, cValue) { - const trappedProp = (( ) => { - const pos = chain.lastIndexOf('.'); - if ( pos === -1 ) { return chain; } - return chain.slice(pos+1); - })(); - if ( trappedProp === '' ) { return; } - const thisScript = document.currentScript; - const objectDefineProperty = Object.defineProperty.bind(Object); - const cloakFunc = fn => { - objectDefineProperty(fn, 'name', { value: trappedProp }); - const proxy = new Proxy(fn, { - defineProperty(target, prop) { - if ( prop !== 'toString' ) { - return Reflect.deleteProperty(...arguments); - } - return true; - }, - deleteProperty(target, prop) { - if ( prop !== 'toString' ) { - return Reflect.deleteProperty(...arguments); - } - return true; - }, - get(target, prop) { - if ( prop === 'toString' ) { - return function() { - return `function ${trappedProp}() { [native code] }`; - }.bind(null); - } - return Reflect.get(...arguments); - }, - }); - return proxy; - }; - if ( cValue === 'undefined' ) { - cValue = undefined; - } else if ( cValue === 'false' ) { - cValue = false; - } else if ( cValue === 'true' ) { - cValue = true; - } else if ( cValue === 'null' ) { - cValue = null; - } else if ( cValue === "''" ) { - cValue = ''; - } else if ( cValue === '[]' ) { - cValue = []; - } else if ( cValue === '{}' ) { - cValue = {}; - } else if ( cValue === 'noopFunc' ) { - cValue = cloakFunc(function(){}); - } else if ( cValue === 'trueFunc' ) { - cValue = cloakFunc(function(){ return true; }); - } else if ( cValue === 'falseFunc' ) { - cValue = cloakFunc(function(){ return false; }); - } else if ( /^-?\d+$/.test(cValue) ) { - cValue = parseInt(cValue); - if ( isNaN(cValue) ) { return; } - if ( Math.abs(cValue) > 0x7FFF ) { return; } - } else { - return; - } - if ( options.includes('asFunction') ) { - cValue = ( ) => cValue; - } else if ( options.includes('asCallback') ) { - cValue = ( ) => (( ) => cValue); - } else if ( options.includes('asResolved') ) { - cValue = Promise.resolve(cValue); - } else if ( options.includes('asRejected') ) { - cValue = Promise.reject(cValue); - } - let aborted = false; - const mustAbort = function(v) { - if ( aborted ) { return true; } - aborted = - (v !== undefined && v !== null) && - (cValue !== undefined && cValue !== null) && - (typeof v !== typeof cValue); - return aborted; - }; - // https://github.com/uBlockOrigin/uBlock-issues/issues/156 - // Support multiple trappers for the same property. - const trapProp = function(owner, prop, configurable, handler) { - if ( handler.init(configurable ? owner[prop] : cValue) === false ) { return; } - const odesc = Object.getOwnPropertyDescriptor(owner, prop); - let prevGetter, prevSetter; - if ( odesc instanceof Object ) { - owner[prop] = cValue; - if ( odesc.get instanceof Function ) { - prevGetter = odesc.get; - } - if ( odesc.set instanceof Function ) { - prevSetter = odesc.set; - } - } - try { - objectDefineProperty(owner, prop, { - configurable, - get() { - if ( prevGetter !== undefined ) { - prevGetter(); - } - return handler.getter(); // cValue - }, - set(a) { - if ( prevSetter !== undefined ) { - prevSetter(a); - } - handler.setter(a); - } - }); - } catch(ex) { - } - }; - const trapChain = function(owner, chain) { - const pos = chain.indexOf('.'); - if ( pos === -1 ) { - trapProp(owner, chain, false, { - v: undefined, - init: function(v) { - if ( mustAbort(v) ) { return false; } - this.v = v; - return true; - }, - getter: function() { - return document.currentScript === thisScript - ? this.v - : cValue; - }, - setter: function(a) { - if ( mustAbort(a) === false ) { return; } - cValue = a; - } - }); - return; - } - const prop = chain.slice(0, pos); - const v = owner[prop]; - chain = chain.slice(pos + 1); - if ( v instanceof Object || typeof v === 'object' && v !== null ) { - trapChain(v, chain); - return; - } - trapProp(owner, prop, true, { - v: undefined, - init: function(v) { - this.v = v; - return true; - }, - getter: function() { - return this.v; - }, - setter: function(a) { - this.v = a; - if ( a instanceof Object ) { - trapChain(a, chain); - } - } - }); - }; - trapChain(window, chain); - } - runAt(( ) => { - setConstant(chain, cValue); - }, options); -}; - -/******************************************************************************/ - -const hnparts = []; -try { hnparts.push(...document.location.hostname.split('.')); } catch(ex) { } -const hnpartslen = hnparts.length - 1; -for ( let i = 0; i < hnpartslen; i++ ) { - for ( let j = hnpartslen; j > i; j-- ) { - const hn = hnparts.slice(i).join('.'); - const en = hnparts.slice(i,j).join('.'); - let argsIndices = entitiesMap.get(en); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } -} - -argsList.length = 0; -entitiesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/scriptlets/set-constant.js b/platform/mv3/scriptlets/set-constant.js deleted file mode 100644 index 6652cbee9..000000000 --- a/platform/mv3/scriptlets/set-constant.js +++ /dev/null @@ -1,316 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-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 - - The scriptlets below are meant to be injected only into a - web page context. -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -/// name set-constant -/// alias set - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_setConstant() { - -/******************************************************************************/ - -// $rulesetId$ - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -/******************************************************************************/ - -// Dependencies - -const scriptletGlobals = new Map(); - -function safeSelf() { - if ( scriptletGlobals.has('safeSelf') ) { - return scriptletGlobals.get('safeSelf'); - } - const safe = { - 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, - 'RegExp_exec': self.RegExp.prototype.exec, - 'addEventListener': self.EventTarget.prototype.addEventListener, - 'removeEventListener': self.EventTarget.prototype.removeEventListener, - }; - scriptletGlobals.set('safeSelf', safe); - return safe; -} - -function runAt(fn, when) { - const intFromReadyState = state => { - const targets = { - 'loading': 1, - 'interactive': 2, 'end': 2, '2': 2, - 'complete': 3, 'idle': 3, '3': 3, - }; - const tokens = Array.isArray(state) ? state : [ state ]; - for ( const token of tokens ) { - const prop = `${token}`; - if ( targets.hasOwnProperty(prop) === false ) { continue; } - return targets[prop]; - } - return 0; - }; - const runAt = intFromReadyState(when); - if ( intFromReadyState(document.readyState) >= runAt ) { - fn(); return; - } - const onStateChange = ( ) => { - if ( intFromReadyState(document.readyState) < runAt ) { return; } - fn(); - safe.removeEventListener.apply(document, args); - }; - const safe = safeSelf(); - const args = [ 'readystatechange', onStateChange, { capture: true } ]; - safe.addEventListener.apply(document, args); -} - -/******************************************************************************/ - -const scriptlet = ( - arg1 = '', - arg2 = '', - arg3 = '' -) => { - const details = typeof arg1 !== 'object' - ? { prop: arg1, value: arg2 } - : arg1; - if ( arg3 !== '' ) { - if ( /^\d$/.test(arg3) ) { - details.options = [ arg3 ]; - } else { - details.options = Array.from(arguments).slice(2); - } - } - const { prop: chain = '', value: cValue = '' } = details; - if ( typeof chain !== 'string' ) { return; } - if ( chain === '' ) { return; } - const options = details.options || []; - function setConstant(chain, cValue) { - const trappedProp = (( ) => { - const pos = chain.lastIndexOf('.'); - if ( pos === -1 ) { return chain; } - return chain.slice(pos+1); - })(); - if ( trappedProp === '' ) { return; } - const thisScript = document.currentScript; - const objectDefineProperty = Object.defineProperty.bind(Object); - const cloakFunc = fn => { - objectDefineProperty(fn, 'name', { value: trappedProp }); - const proxy = new Proxy(fn, { - defineProperty(target, prop) { - if ( prop !== 'toString' ) { - return Reflect.deleteProperty(...arguments); - } - return true; - }, - deleteProperty(target, prop) { - if ( prop !== 'toString' ) { - return Reflect.deleteProperty(...arguments); - } - return true; - }, - get(target, prop) { - if ( prop === 'toString' ) { - return function() { - return `function ${trappedProp}() { [native code] }`; - }.bind(null); - } - return Reflect.get(...arguments); - }, - }); - return proxy; - }; - if ( cValue === 'undefined' ) { - cValue = undefined; - } else if ( cValue === 'false' ) { - cValue = false; - } else if ( cValue === 'true' ) { - cValue = true; - } else if ( cValue === 'null' ) { - cValue = null; - } else if ( cValue === "''" ) { - cValue = ''; - } else if ( cValue === '[]' ) { - cValue = []; - } else if ( cValue === '{}' ) { - cValue = {}; - } else if ( cValue === 'noopFunc' ) { - cValue = cloakFunc(function(){}); - } else if ( cValue === 'trueFunc' ) { - cValue = cloakFunc(function(){ return true; }); - } else if ( cValue === 'falseFunc' ) { - cValue = cloakFunc(function(){ return false; }); - } else if ( /^-?\d+$/.test(cValue) ) { - cValue = parseInt(cValue); - if ( isNaN(cValue) ) { return; } - if ( Math.abs(cValue) > 0x7FFF ) { return; } - } else { - return; - } - if ( options.includes('asFunction') ) { - cValue = ( ) => cValue; - } else if ( options.includes('asCallback') ) { - cValue = ( ) => (( ) => cValue); - } else if ( options.includes('asResolved') ) { - cValue = Promise.resolve(cValue); - } else if ( options.includes('asRejected') ) { - cValue = Promise.reject(cValue); - } - let aborted = false; - const mustAbort = function(v) { - if ( aborted ) { return true; } - aborted = - (v !== undefined && v !== null) && - (cValue !== undefined && cValue !== null) && - (typeof v !== typeof cValue); - return aborted; - }; - // https://github.com/uBlockOrigin/uBlock-issues/issues/156 - // Support multiple trappers for the same property. - const trapProp = function(owner, prop, configurable, handler) { - if ( handler.init(configurable ? owner[prop] : cValue) === false ) { return; } - const odesc = Object.getOwnPropertyDescriptor(owner, prop); - let prevGetter, prevSetter; - if ( odesc instanceof Object ) { - owner[prop] = cValue; - if ( odesc.get instanceof Function ) { - prevGetter = odesc.get; - } - if ( odesc.set instanceof Function ) { - prevSetter = odesc.set; - } - } - try { - objectDefineProperty(owner, prop, { - configurable, - get() { - if ( prevGetter !== undefined ) { - prevGetter(); - } - return handler.getter(); // cValue - }, - set(a) { - if ( prevSetter !== undefined ) { - prevSetter(a); - } - handler.setter(a); - } - }); - } catch(ex) { - } - }; - const trapChain = function(owner, chain) { - const pos = chain.indexOf('.'); - if ( pos === -1 ) { - trapProp(owner, chain, false, { - v: undefined, - init: function(v) { - if ( mustAbort(v) ) { return false; } - this.v = v; - return true; - }, - getter: function() { - return document.currentScript === thisScript - ? this.v - : cValue; - }, - setter: function(a) { - if ( mustAbort(a) === false ) { return; } - cValue = a; - } - }); - return; - } - const prop = chain.slice(0, pos); - const v = owner[prop]; - chain = chain.slice(pos + 1); - if ( v instanceof Object || typeof v === 'object' && v !== null ) { - trapChain(v, chain); - return; - } - trapProp(owner, prop, true, { - v: undefined, - init: function(v) { - this.v = v; - return true; - }, - getter: function() { - return this.v; - }, - setter: function(a) { - this.v = a; - if ( a instanceof Object ) { - trapChain(a, chain); - } - } - }); - }; - trapChain(window, chain); - } - runAt(( ) => { - setConstant(chain, cValue); - }, options); -}; - -/******************************************************************************/ - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - try { scriptlet(...details.a); } catch(ex) {} - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -argsList.length = 0; -hostnamesMap.clear(); - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/src/js/static-dnr-filtering.js b/src/js/static-dnr-filtering.js index c5f0fee5c..c72461fb8 100644 --- a/src/js/static-dnr-filtering.js +++ b/src/js/static-dnr-filtering.js @@ -96,13 +96,27 @@ function addExtendedToDNR(context, parser) { context.scriptletFilters = new Map(); } const exception = parser.isException(); - const raw = parser.getTypeString(sfp.NODE_TYPE_EXT_PATTERN_RAW); + const root = parser.getBranchFromType(sfp.NODE_TYPE_EXT_PATTERN_SCRIPTLET); + const walker = parser.getWalker(root); + const args = []; + for ( let node = walker.next(); node !== 0; node = walker.next() ) { + switch ( parser.getNodeType(node) ) { + case sfp.NODE_TYPE_EXT_PATTERN_SCRIPTLET_TOKEN: + case sfp.NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARG: + args.push(parser.getNodeString(node)); + break; + default: + break; + } + } + walker.dispose(); + const argsToken = JSON.stringify(args); for ( const { hn, not, bad } of parser.getExtFilterDomainIterator() ) { if ( bad ) { continue; } if ( exception ) { continue; } - let details = context.scriptletFilters.get(raw); + let details = context.scriptletFilters.get(argsToken); if ( details === undefined ) { - context.scriptletFilters.set(raw, details = {}); + context.scriptletFilters.set(argsToken, details = { args }); } if ( not ) { if ( details.excludeMatches === undefined ) { diff --git a/tools/make-mv3.sh b/tools/make-mv3.sh index 1c6a571c5..c316ebfa6 100755 --- a/tools/make-mv3.sh +++ b/tools/make-mv3.sh @@ -82,6 +82,7 @@ if [ "$QUICK" != "yes" ]; then cp platform/mv3/*.js $TMPDIR/ cp platform/mv3/extension/js/utils.js $TMPDIR/js/ cp assets/assets.json $TMPDIR/ + cp assets/resources/scriptlets.js $TMPDIR/ cp -R platform/mv3/scriptlets $TMPDIR/ mkdir -p $TMPDIR/web_accessible_resources cp src/web_accessible_resources/* $TMPDIR/web_accessible_resources/