Add scriptlet dependencies to reduce code duplication

This commit is contained in:
Raymond Hill 2023-03-26 09:13:17 -04:00
parent 439951824a
commit 236fb3ad59
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
3 changed files with 546 additions and 605 deletions

View file

@ -26,11 +26,64 @@
export const builtinScriptlets = []; export const builtinScriptlets = [];
/// abort-current-script.js /*******************************************************************************
Helper functions
These are meant to be used as dependencies to injectable scriptlets.
*******************************************************************************/
builtinScriptlets.push({
name: 'pattern-to-regex.fn',
fn: patternToRegex,
});
function patternToRegex(pattern) {
if ( pattern === '' ) {
return /^/;
}
if ( pattern.startsWith('/') && pattern.endsWith('/') ) {
return new RegExp(pattern.slice(1, -1));
}
return new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
}
/******************************************************************************/
builtinScriptlets.push({
name: 'get-exception-token.fn',
fn: getExceptionToken,
});
function getExceptionToken() {
const token =
String.fromCharCode(Date.now() % 26 + 97) +
Math.floor(Math.random() * 982451653 + 982451653).toString(36);
const oe = self.onerror;
self.onerror = function(msg, ...args) {
if ( typeof msg === 'string' && msg.includes(token) ) { return true; }
if ( oe instanceof Function ) {
return oe.call(this, msg, ...args);
}
}.bind();
return token;
}
/*******************************************************************************
Injectable scriptlets
These are meant to be used in the MAIN (webpage) execution world.
*******************************************************************************/
builtinScriptlets.push({ builtinScriptlets.push({
name: 'abort-current-script.js', name: 'abort-current-script.js',
aliases: [ 'acs.js', 'abort-current-inline-script.js', 'acis.js' ], aliases: [ 'acs.js', 'abort-current-inline-script.js', 'acis.js' ],
fn: abortCurrentScript, fn: abortCurrentScript,
dependencies: [
'pattern-to-regex.fn',
'get-exception-token.fn',
],
}); });
// Issues to mind before changing anything: // Issues to mind before changing anything:
// https://github.com/uBlockOrigin/uBlock-issues/issues/2154 // https://github.com/uBlockOrigin/uBlock-issues/issues/2154
@ -41,21 +94,8 @@ function abortCurrentScript(
) { ) {
if ( typeof target !== 'string' ) { return; } if ( typeof target !== 'string' ) { return; }
if ( target === '' ) { return; } if ( target === '' ) { return; }
const reRegexEscape = /[.*+?^${}()|[\]\\]/g; const reNeedle = patternToRegex(needle);
const reNeedle = (( ) => { const reContext = patternToRegex(context);
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 thisScript = document.currentScript; const thisScript = document.currentScript;
const chain = target.split('.'); const chain = target.split('.');
let owner = window; let owner = window;
@ -75,8 +115,7 @@ function abortCurrentScript(
value = owner[prop]; value = owner[prop];
desc = undefined; desc = undefined;
} }
const magic = String.fromCharCode(Date.now() % 26 + 97) + const exceptionToken = getExceptionToken();
Math.floor(Math.random() * 982451653 + 982451653).toString(36);
const scriptTexts = new WeakMap(); const scriptTexts = new WeakMap();
const getScriptText = elem => { const getScriptText = elem => {
let text = elem.textContent; let text = elem.textContent;
@ -103,11 +142,9 @@ function abortCurrentScript(
const e = document.currentScript; const e = document.currentScript;
if ( e instanceof HTMLScriptElement === false ) { return; } if ( e instanceof HTMLScriptElement === false ) { return; }
if ( e === thisScript ) { return; } if ( e === thisScript ) { return; }
if ( reContext !== undefined && reContext.test(e.src) === false ) { if ( reContext.test(e.src) === false ) { return; }
return;
}
if ( reNeedle.test(getScriptText(e)) === false ) { return; } if ( reNeedle.test(getScriptText(e)) === false ) { return; }
throw new ReferenceError(magic); throw new ReferenceError(exceptionToken);
}; };
Object.defineProperty(owner, prop, { Object.defineProperty(owner, prop, {
get: function() { get: function() {
@ -125,33 +162,26 @@ function abortCurrentScript(
} }
} }
}); });
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();
} }
/******************************************************************************/
/// abort-on-property-read.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'abort-on-property-read.js', name: 'abort-on-property-read.js',
aliases: [ 'aopr.js' ], aliases: [ 'aopr.js' ],
fn: abortOnPropertyRead, fn: abortOnPropertyRead,
dependencies: [
'get-exception-token.fn',
],
}); });
function abortOnPropertyRead( function abortOnPropertyRead(
chain = '' chain = ''
) { ) {
if ( typeof chain !== 'string' ) { return; } if ( typeof chain !== 'string' ) { return; }
if ( chain === '' ) { return; } if ( chain === '' ) { return; }
const magic = String.fromCharCode(Date.now() % 26 + 97) + const exceptionToken = getExceptionToken();
Math.floor(Math.random() * 982451653 + 982451653).toString(36);
const abort = function() { const abort = function() {
throw new ReferenceError(magic); throw new ReferenceError(exceptionToken);
}; };
const makeProxy = function(owner, chain) { const makeProxy = function(owner, chain) {
const pos = chain.indexOf('.'); const pos = chain.indexOf('.');
@ -186,31 +216,24 @@ function abortOnPropertyRead(
}; };
const owner = window; const owner = window;
makeProxy(owner, chain); makeProxy(owner, chain);
const oe = window.onerror;
window.onerror = function(msg, src, line, col, error) {
if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) {
return true;
}
if ( oe instanceof Function ) {
return oe(msg, src, line, col, error);
}
}.bind();
} }
/******************************************************************************/
/// abort-on-property-write.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'abort-on-property-write.js', name: 'abort-on-property-write.js',
aliases: [ 'aopw.js' ], aliases: [ 'aopw.js' ],
fn: abortOnPropertyWrite, fn: abortOnPropertyWrite,
dependencies: [
'get-exception-token.fn',
],
}); });
function abortOnPropertyWrite( function abortOnPropertyWrite(
prop = '' prop = ''
) { ) {
if ( typeof prop !== 'string' ) { return; } if ( typeof prop !== 'string' ) { return; }
if ( prop === '' ) { return; } if ( prop === '' ) { return; }
const magic = String.fromCharCode(Date.now() % 26 + 97) + const exceptionToken = getExceptionToken();
Math.floor(Math.random() * 982451653 + 982451653).toString(36);
let owner = window; let owner = window;
for (;;) { for (;;) {
const pos = prop.indexOf('.'); const pos = prop.indexOf('.');
@ -222,26 +245,21 @@ function abortOnPropertyWrite(
delete owner[prop]; delete owner[prop];
Object.defineProperty(owner, prop, { Object.defineProperty(owner, prop, {
set: function() { set: function() {
throw new ReferenceError(magic); throw new ReferenceError(exceptionToken);
} }
}); });
const oe = window.onerror;
window.onerror = function(msg, src, line, col, error) {
if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) {
return true;
}
if ( oe instanceof Function ) {
return oe(msg, src, line, col, error);
}
}.bind();
} }
/******************************************************************************/
/// abort-on-stack-trace.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'abort-on-stack-trace.js', name: 'abort-on-stack-trace.js',
aliases: [ 'aost.js' ], aliases: [ 'aost.js' ],
fn: abortOnStackTrace, fn: abortOnStackTrace,
dependencies: [
'pattern-to-regex.fn',
'get-exception-token.fn',
],
}); });
// Status is currently experimental // Status is currently experimental
function abortOnStackTrace( function abortOnStackTrace(
@ -250,19 +268,8 @@ function abortOnStackTrace(
logLevel = '' logLevel = ''
) { ) {
if ( typeof chain !== 'string' ) { return; } if ( typeof chain !== 'string' ) { return; }
const reRegexEscape = /[.*+?^${}()|[\]\\]/g; const reNeedle = patternToRegex(needle);
if ( needle === '' ) { const exceptionToken = getExceptionToken();
needle = '^';
} else if ( /^\/.+\/$/.test(needle) ) {
needle = needle.slice(1,-1);
} else {
needle = needle.replace(reRegexEscape, '\\$&');
}
const reNeedle = new RegExp(needle);
const magic = String.fromCharCode(Math.random() * 26 + 97) +
Math.floor(
(0.25 + Math.random() * 0.75) * Number.MAX_SAFE_INTEGER
).toString(36).slice(-8);
const log = console.log.bind(console); const log = console.log.bind(console);
const ErrorCtor = self.Error; const ErrorCtor = self.Error;
const mustAbort = function(err) { const mustAbort = function(err) {
@ -274,7 +281,7 @@ function abortOnStackTrace(
// Normalize stack trace // Normalize stack trace
const lines = []; const lines = [];
for ( let line of err.stack.split(/[\n\r]+/) ) { for ( let line of err.stack.split(/[\n\r]+/) ) {
if ( line.includes(magic) ) { continue; } if ( line.includes(exceptionToken) ) { continue; }
line = line.trim(); line = line.trim();
let match = /(.*?@)?(\S+)(:\d+):\d+\)?$/.exec(line); let match = /(.*?@)?(\S+)(:\d+):\d+\)?$/.exec(line);
if ( match === null ) { continue; } if ( match === null ) { continue; }
@ -310,14 +317,14 @@ function abortOnStackTrace(
let v = owner[chain]; let v = owner[chain];
Object.defineProperty(owner, chain, { Object.defineProperty(owner, chain, {
get: function() { get: function() {
if ( mustAbort(new ErrorCtor(magic)) ) { if ( mustAbort(new ErrorCtor(exceptionToken)) ) {
throw new ReferenceError(magic); throw new ReferenceError(exceptionToken);
} }
return v; return v;
}, },
set: function(a) { set: function(a) {
if ( mustAbort(new ErrorCtor(magic)) ) { if ( mustAbort(new ErrorCtor(exceptionToken)) ) {
throw new ReferenceError(magic); throw new ReferenceError(exceptionToken);
} }
v = a; v = a;
}, },
@ -345,23 +352,17 @@ function abortOnStackTrace(
}; };
const owner = window; const owner = window;
makeProxy(owner, chain); makeProxy(owner, chain);
const oe = window.onerror;
window.onerror = function(msg, src, line, col, error) {
if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) {
return true;
}
if ( oe instanceof Function ) {
return oe(msg, src, line, col, error);
}
}.bind();
} }
/******************************************************************************/
/// addEventListener-defuser.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'addEventListener-defuser.js', name: 'addEventListener-defuser.js',
aliases: [ 'aeld.js' ], aliases: [ 'aeld.js' ],
fn: addEventListenerDefuser, fn: addEventListenerDefuser,
dependencies: [
'pattern-to-regex.fn',
],
}); });
// https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120 // https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120
function addEventListenerDefuser( function addEventListenerDefuser(
@ -374,22 +375,8 @@ function addEventListenerDefuser(
let { type = '', pattern = '' } = details; let { type = '', pattern = '' } = details;
if ( typeof type !== 'string' ) { return; } if ( typeof type !== 'string' ) { return; }
if ( typeof pattern !== 'string' ) { return; } if ( typeof pattern !== 'string' ) { return; }
if ( type === '' ) { const reType = patternToRegex(type);
type = '^'; const rePattern = patternToRegex(pattern);
} else if ( /^\/.+\/$/.test(type) ) {
type = type.slice(1,-1);
} else {
type = type.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const reType = new RegExp(type);
if ( pattern === '' ) {
pattern = '^';
} else if ( /^\/.+\/$/.test(pattern) ) {
pattern = pattern.slice(1,-1);
} else {
pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const rePattern = new RegExp(pattern);
const logfn = console.log.bind(console); const logfn = console.log.bind(console);
const proto = self.EventTarget.prototype; const proto = self.EventTarget.prototype;
proto.addEventListener = new Proxy(proto.addEventListener, { proto.addEventListener = new Proxy(proto.addEventListener, {
@ -423,11 +410,14 @@ function addEventListenerDefuser(
}); });
} }
/******************************************************************************/
/// json-prune.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'json-prune.js', name: 'json-prune.js',
fn: jsonPrune, fn: jsonPrune,
dependencies: [
'pattern-to-regex.fn',
],
}); });
// When no "prune paths" argument is provided, the scriptlet is // When no "prune paths" argument is provided, the scriptlet is
// used for logging purpose and the "needle paths" argument is // used for logging purpose and the "needle paths" argument is
@ -451,15 +441,7 @@ function jsonPrune(
: []; : [];
} else { } else {
log = console.log.bind(console); log = console.log.bind(console);
let needle; reLogNeedle = patternToRegex(rawNeedlePaths);
if ( rawNeedlePaths === '' ) {
needle = '.?';
} else if ( rawNeedlePaths.charAt(0) === '/' && rawNeedlePaths.slice(-1) === '/' ) {
needle = rawNeedlePaths.slice(1, -1);
} else {
needle = rawNeedlePaths.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
reLogNeedle = new RegExp(needle);
} }
const findOwner = function(root, path, prune = false) { const findOwner = function(root, path, prune = false) {
let owner = root; let owner = root;
@ -534,12 +516,15 @@ function jsonPrune(
}); });
} }
/******************************************************************************/
/// nano-setInterval-booster.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'nano-setInterval-booster.js', name: 'nano-setInterval-booster.js',
aliases: [ 'nano-sib.js' ], aliases: [ 'nano-sib.js' ],
fn: nanoSetIntervalBooster, fn: nanoSetIntervalBooster,
dependencies: [
'pattern-to-regex.fn',
],
}); });
// Imported from: // Imported from:
// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L126 // https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L126
@ -559,14 +544,7 @@ function nanoSetIntervalBooster(
boostArg = '' boostArg = ''
) { ) {
if ( typeof needleArg !== 'string' ) { return; } if ( typeof needleArg !== 'string' ) { return; }
if ( needleArg === '' ) { const reNeedle = patternToRegex(needleArg);
needleArg = '.?';
} else if ( needleArg.charAt(0) === '/' && needleArg.slice(-1) === '/' ) {
needleArg = needleArg.slice(1, -1);
} else {
needleArg = needleArg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const reNeedle = new RegExp(needleArg);
let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1; let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1;
if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; } if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; }
let boost = parseFloat(boostArg); let boost = parseFloat(boostArg);
@ -587,12 +565,15 @@ function nanoSetIntervalBooster(
}); });
} }
/******************************************************************************/
/// nano-setTimeout-booster.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'nano-setTimeout-booster.js', name: 'nano-setTimeout-booster.js',
aliases: [ 'nano-stb.js' ], aliases: [ 'nano-stb.js' ],
fn: nanoSetTimeoutBooster, fn: nanoSetTimeoutBooster,
dependencies: [
'pattern-to-regex.fn',
],
}); });
// Imported from: // Imported from:
// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L82 // https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L82
@ -613,14 +594,7 @@ function nanoSetTimeoutBooster(
boostArg = '' boostArg = ''
) { ) {
if ( typeof needleArg !== 'string' ) { return; } if ( typeof needleArg !== 'string' ) { return; }
if ( needleArg === '' ) { const reNeedle = patternToRegex(needleArg);
needleArg = '.?';
} else if ( needleArg.charAt(0) === '/' && needleArg.slice(-1) === '/' ) {
needleArg = needleArg.slice(1, -1);
} else {
needleArg = needleArg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const reNeedle = new RegExp(needleArg);
let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1; let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1;
if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; } if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; }
let boost = parseFloat(boostArg); let boost = parseFloat(boostArg);
@ -641,39 +615,37 @@ function nanoSetTimeoutBooster(
}); });
} }
/******************************************************************************/
/// noeval-if.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'noeval-if.js', name: 'noeval-if.js',
fn: noevalIf, fn: noEvalIf,
dependencies: [
'pattern-to-regex.fn',
],
}); });
function noevalIf( function noEvalIf(
needle = '' needle = ''
) { ) {
if ( typeof needle !== 'string' ) { return; } if ( typeof needle !== 'string' ) { return; }
if ( needle === '' ) { const reNeedle = patternToRegex(needle);
needle = '.?';
} else if ( needle.slice(0,1) === '/' && needle.slice(-1) === '/' ) {
needle = needle.slice(1,-1);
} else {
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
needle = new RegExp(needle);
window.eval = new Proxy(window.eval, { // jshint ignore: line window.eval = new Proxy(window.eval, { // jshint ignore: line
apply: function(target, thisArg, args) { apply: function(target, thisArg, args) {
const a = args[0]; const a = args[0];
if ( needle.test(a.toString()) === false ) { if ( reNeedle.test(a.toString()) ) { return; }
return target.apply(thisArg, args); return target.apply(thisArg, args);
} }
}
}); });
} }
/******************************************************************************/
/// no-fetch-if.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'no-fetch-if.js', name: 'no-fetch-if.js',
fn: noFetchIf, fn: noFetchIf,
dependencies: [
'pattern-to-regex.fn',
],
}); });
function noFetchIf( function noFetchIf(
arg1 = '', arg1 = '',
@ -691,14 +663,7 @@ function noFetchIf(
key = 'url'; key = 'url';
value = condition; value = condition;
} }
if ( value === '' ) { needles.push({ key, re: patternToRegex(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) });
} }
const log = needles.length === 0 ? console.log.bind(console) : undefined; const log = needles.length === 0 ? console.log.bind(console) : undefined;
self.fetch = new Proxy(self.fetch, { self.fetch = new Proxy(self.fetch, {
@ -746,8 +711,8 @@ function noFetchIf(
}); });
} }
/******************************************************************************/
/// refresh-defuser.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'refresh-defuser.js', name: 'refresh-defuser.js',
fn: refreshDefuser, fn: refreshDefuser,
@ -773,8 +738,8 @@ function refreshDefuser(
} }
} }
/******************************************************************************/
/// remove-attr.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'remove-attr.js', name: 'remove-attr.js',
aliases: [ 'ra.js' ], aliases: [ 'ra.js' ],
@ -840,8 +805,8 @@ function removeAttr(
} }
} }
/******************************************************************************/
/// remove-class.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'remove-class.js', name: 'remove-class.js',
aliases: [ 'rc.js' ], aliases: [ 'rc.js' ],
@ -905,12 +870,15 @@ function removeClass(
} }
} }
/******************************************************************************/
/// no-requestAnimationFrame-if.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'no-requestAnimationFrame-if.js', name: 'no-requestAnimationFrame-if.js',
aliases: [ 'norafif.js' ], aliases: [ 'norafif.js' ],
fn: noRequestAnimationFrameIf, fn: noRequestAnimationFrameIf,
dependencies: [
'pattern-to-regex.fn',
],
}); });
function noRequestAnimationFrameIf( function noRequestAnimationFrameIf(
needle = '' needle = ''
@ -918,13 +886,8 @@ function noRequestAnimationFrameIf(
if ( typeof needle !== 'string' ) { return; } if ( typeof needle !== 'string' ) { return; }
const needleNot = needle.charAt(0) === '!'; const needleNot = needle.charAt(0) === '!';
if ( needleNot ) { needle = needle.slice(1); } if ( needleNot ) { needle = needle.slice(1); }
if ( needle.startsWith('/') && needle.endsWith('/') ) {
needle = needle.slice(1, -1);
} else if ( needle !== '' ) {
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const log = needleNot === false && needle === '' ? console.log : undefined; const log = needleNot === false && needle === '' ? console.log : undefined;
const reNeedle = new RegExp(needle); const reNeedle = patternToRegex(needle);
window.requestAnimationFrame = new Proxy(window.requestAnimationFrame, { window.requestAnimationFrame = new Proxy(window.requestAnimationFrame, {
apply: function(target, thisArg, args) { apply: function(target, thisArg, args) {
const a = String(args[0]); const a = String(args[0]);
@ -942,8 +905,8 @@ function noRequestAnimationFrameIf(
}); });
} }
/******************************************************************************/
/// set-constant.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'set-constant.js', name: 'set-constant.js',
aliases: [ 'set.js' ], aliases: [ 'set.js' ],
@ -1108,12 +1071,15 @@ function setConstant(
trapChain(window, chain); trapChain(window, chain);
} }
/******************************************************************************/
/// no-setInterval-if.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'no-setInterval-if.js', name: 'no-setInterval-if.js',
aliases: [ 'nosiif.js' ], aliases: [ 'nosiif.js' ],
fn: noSetIntervalIf, fn: noSetIntervalIf,
dependencies: [
'pattern-to-regex.fn',
],
}); });
function noSetIntervalIf( function noSetIntervalIf(
needle = '', needle = '',
@ -1129,17 +1095,10 @@ function noSetIntervalIf(
if ( delayNot ) { delay = delay.slice(1); } if ( delayNot ) { delay = delay.slice(1); }
delay = parseInt(delay, 10); delay = parseInt(delay, 10);
} }
if ( needle === '' ) {
needle = '';
} else if ( needle.startsWith('/') && needle.endsWith('/') ) {
needle = needle.slice(1,-1);
} else {
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const log = needleNot === false && needle === '' && delay === undefined const log = needleNot === false && needle === '' && delay === undefined
? console.log ? console.log
: undefined; : undefined;
const reNeedle = new RegExp(needle); const reNeedle = patternToRegex(needle);
window.setInterval = new Proxy(window.setInterval, { window.setInterval = new Proxy(window.setInterval, {
apply: function(target, thisArg, args) { apply: function(target, thisArg, args) {
const a = String(args[0]); const a = String(args[0]);
@ -1163,12 +1122,15 @@ function noSetIntervalIf(
}); });
} }
/******************************************************************************/
/// no-setTimeout-if.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'no-setTimeout-if.js', name: 'no-setTimeout-if.js',
aliases: [ 'nostif.js', 'setTimeout-defuser.js' ], aliases: [ 'nostif.js', 'setTimeout-defuser.js' ],
fn: noSetTimeoutIf, fn: noSetTimeoutIf,
dependencies: [
'pattern-to-regex.fn',
],
}); });
function noSetTimeoutIf( function noSetTimeoutIf(
needle = '', needle = '',
@ -1184,17 +1146,10 @@ function noSetTimeoutIf(
if ( delayNot ) { delay = delay.slice(1); } if ( delayNot ) { delay = delay.slice(1); }
delay = parseInt(delay, 10); delay = parseInt(delay, 10);
} }
if ( needle === '' ) {
needle = '';
} else if ( needle.startsWith('/') && needle.endsWith('/') ) {
needle = needle.slice(1,-1);
} else {
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const log = needleNot === false && needle === '' && delay === undefined const log = needleNot === false && needle === '' && delay === undefined
? console.log ? console.log
: undefined; : undefined;
const reNeedle = new RegExp(needle); const reNeedle = patternToRegex(needle);
window.setTimeout = new Proxy(window.setTimeout, { window.setTimeout = new Proxy(window.setTimeout, {
apply: function(target, thisArg, args) { apply: function(target, thisArg, args) {
const a = String(args[0]); const a = String(args[0]);
@ -1218,27 +1173,20 @@ function noSetTimeoutIf(
}); });
} }
/******************************************************************************/
/// webrtc-if.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'webrtc-if.js', name: 'webrtc-if.js',
fn: webrtcIf, fn: webrtcIf,
dependencies: [
'pattern-to-regex.fn',
],
}); });
function webrtcIf( function webrtcIf(
good = '' good = ''
) { ) {
if ( typeof good !== 'string' ) { return; } if ( typeof good !== 'string' ) { return; }
if ( good.startsWith('/') && good.endsWith('/') ) { const reGood = patternToRegex(good);
good = good.slice(1, -1);
} else {
good = good.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
let reGood;
try {
reGood = new RegExp(good);
} catch(ex) {
return;
}
const rtcName = window.RTCPeerConnection const rtcName = window.RTCPeerConnection
? 'RTCPeerConnection' ? 'RTCPeerConnection'
: (window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : ''); : (window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : '');
@ -1292,11 +1240,14 @@ function webrtcIf(
}); });
} }
/******************************************************************************/
/// no-xhr-if.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'no-xhr-if.js', name: 'no-xhr-if.js',
fn: noXhrIf, fn: noXhrIf,
dependencies: [
'pattern-to-regex.fn',
],
}); });
function noXhrIf( function noXhrIf(
arg1 = '' arg1 = ''
@ -1315,14 +1266,7 @@ function noXhrIf(
key = 'url'; key = 'url';
value = condition; value = condition;
} }
if ( value === '' ) { needles.push({ key, re: patternToRegex(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) });
} }
const log = needles.length === 0 ? console.log.bind(console) : undefined; const log = needles.length === 0 ? console.log.bind(console) : undefined;
self.XMLHttpRequest = class extends self.XMLHttpRequest { self.XMLHttpRequest = class extends self.XMLHttpRequest {
@ -1369,11 +1313,14 @@ function noXhrIf(
}; };
} }
/******************************************************************************/
/// window-close-if.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'window-close-if.js', name: 'window-close-if.js',
fn: windowCloseIf, fn: windowCloseIf,
dependencies: [
'pattern-to-regex.fn',
],
}); });
// https://github.com/uBlockOrigin/uAssets/issues/10323#issuecomment-992312847 // https://github.com/uBlockOrigin/uAssets/issues/10323#issuecomment-992312847
// https://github.com/AdguardTeam/Scriptlets/issues/158 // https://github.com/AdguardTeam/Scriptlets/issues/158
@ -1382,19 +1329,14 @@ function windowCloseIf(
arg1 = '' arg1 = ''
) { ) {
if ( typeof arg1 !== 'string' ) { return; } if ( typeof arg1 !== 'string' ) { return; }
let reStr;
let subject = ''; let subject = '';
if ( arg1 === '' ) { if ( /^\/.*\/$/.test(arg1) ) {
reStr = '^';
} else if ( /^\/.*\/$/.test(arg1) ) {
reStr = arg1.slice(1, -1);
subject = window.location.href; subject = window.location.href;
} else { } else if ( arg1 !== '' ) {
reStr = arg1.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
subject = `${window.location.pathname}${window.location.search}`; subject = `${window.location.pathname}${window.location.search}`;
} }
try { try {
const re = new RegExp(reStr); const re = patternToRegex(arg1);
if ( re.test(subject) ) { if ( re.test(subject) ) {
window.close(); window.close();
} }
@ -1403,8 +1345,8 @@ function windowCloseIf(
} }
} }
/******************************************************************************/
/// window.name-defuser.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'window.name-defuser.js', name: 'window.name-defuser.js',
fn: windowNameDefuser, fn: windowNameDefuser,
@ -1416,8 +1358,8 @@ function windowNameDefuser() {
} }
} }
/******************************************************************************/
/// overlay-buster.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'overlay-buster.js', name: 'overlay-buster.js',
fn: overlayBuster, fn: overlayBuster,
@ -1476,8 +1418,8 @@ function overlayBuster() {
} }
} }
/******************************************************************************/
/// alert-buster.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'alert-buster.js', name: 'alert-buster.js',
fn: alertBuster, fn: alertBuster,
@ -1491,8 +1433,8 @@ function alertBuster() {
}); });
} }
/******************************************************************************/
/// nowebrtc.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'nowebrtc.js', name: 'nowebrtc.js',
fn: noWebrtc, fn: noWebrtc,
@ -1530,8 +1472,8 @@ function noWebrtc() {
} }
} }
/******************************************************************************/
/// golem.de.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'golem.de.js', name: 'golem.de.js',
fn: golemDe, fn: golemDe,
@ -1555,8 +1497,8 @@ function golemDe() {
}.bind(window); }.bind(window);
} }
/******************************************************************************/
/// adfly-defuser.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'adfly-defuser.js', name: 'adfly-defuser.js',
fn: adflyDefuser, fn: adflyDefuser,
@ -1623,8 +1565,8 @@ function adflyDefuser() {
} }
} }
/******************************************************************************/
/// disable-newtab-links.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'disable-newtab-links.js', name: 'disable-newtab-links.js',
fn: disableNewtabLinks, fn: disableNewtabLinks,
@ -1644,23 +1586,21 @@ function disableNewtabLinks() {
}); });
} }
/******************************************************************************/
/// cookie-remover.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'cookie-remover.js', name: 'cookie-remover.js',
fn: cookieRemover, fn: cookieRemover,
dependencies: [
'pattern-to-regex.fn',
],
}); });
// https://github.com/NanoAdblocker/NanoFilters/issues/149 // https://github.com/NanoAdblocker/NanoFilters/issues/149
function cookieRemover( function cookieRemover(
needle = '' needle = ''
) { ) {
if ( typeof needle !== 'string' ) { return; } if ( typeof needle !== 'string' ) { return; }
let reName = /./; const reName = patternToRegex(needle);
if ( /^\/.+\/$/.test(needle) ) {
reName = new RegExp(needle.slice(1,-1));
} else if ( needle !== '' ) {
reName = new RegExp(needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
}
const removeCookie = function() { const removeCookie = function() {
document.cookie.split(';').forEach(cookieStr => { document.cookie.split(';').forEach(cookieStr => {
let pos = cookieStr.indexOf('='); let pos = cookieStr.indexOf('=');
@ -1700,11 +1640,14 @@ function cookieRemover(
window.addEventListener('beforeunload', removeCookie); window.addEventListener('beforeunload', removeCookie);
} }
/******************************************************************************/
/// xml-prune.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'xml-prune.js', name: 'xml-prune.js',
fn: xmlPrune, fn: xmlPrune,
dependencies: [
'pattern-to-regex.fn',
],
}); });
function xmlPrune( function xmlPrune(
selector = '', selector = '',
@ -1713,14 +1656,7 @@ function xmlPrune(
) { ) {
if ( typeof selector !== 'string' ) { return; } if ( typeof selector !== 'string' ) { return; }
if ( selector === '' ) { return; } if ( selector === '' ) { return; }
let reUrl; const reUrl = patternToRegex(urlPattern);
if ( urlPattern === '' ) {
reUrl = /^/;
} else if ( /^\/.*\/$/.test(urlPattern) ) {
reUrl = new RegExp(urlPattern.slice(1, -1));
} else {
reUrl = new RegExp(urlPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
}
const pruner = text => { const pruner = text => {
if ( (/^\s*</.test(text) && />\s*$/.test(text)) === false ) { if ( (/^\s*</.test(text) && />\s*$/.test(text)) === false ) {
return text; return text;
@ -1767,8 +1703,8 @@ function xmlPrune(
}); });
} }
/******************************************************************************/
/// m3u-prune.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'm3u-prune.js', name: 'm3u-prune.js',
fn: m3uPrune, fn: m3uPrune,
@ -1889,8 +1825,8 @@ function m3uPrune(
}); });
} }
/******************************************************************************/
/// href-sanitizer.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'href-sanitizer.js', name: 'href-sanitizer.js',
fn: hrefSanitizer, fn: hrefSanitizer,
@ -1980,8 +1916,8 @@ function hrefSanitizer(
} }
} }
/******************************************************************************/
/// call-nothrow.js
builtinScriptlets.push({ builtinScriptlets.push({
name: 'call-nothrow.js', name: 'call-nothrow.js',
fn: callNothrow, fn: callNothrow,
@ -2014,3 +1950,4 @@ function callNothrow(
}); });
} }
/******************************************************************************/

View file

@ -34,6 +34,7 @@ import {
const extToMimeMap = new Map([ const extToMimeMap = new Map([
[ 'css', 'text/css' ], [ 'css', 'text/css' ],
[ 'fn', 'fn/javascript' ], // invented mime type for internal use
[ 'gif', 'image/gif' ], [ 'gif', 'image/gif' ],
[ 'html', 'text/html' ], [ 'html', 'text/html' ],
[ 'js', 'text/javascript' ], [ 'js', 'text/javascript' ],
@ -55,11 +56,14 @@ const typeToMimeMap = new Map([
const validMimes = new Set(extToMimeMap.values()); const validMimes = new Set(extToMimeMap.values());
const mimeFromName = function(name) { const mimeFromName = name => {
const match = /\.([^.]+)$/.exec(name); const match = /\.([^.]+)$/.exec(name);
if ( match !== null ) { if ( match === null ) { return ''; }
return extToMimeMap.get(match[1]); return extToMimeMap.get(match[1]);
} };
const removeTopCommentBlock = text => {
return text.replace(/^\/\*[\S\s]+?\n\*\/\s*/, '');
}; };
// vAPI.warSecret() is optional, it could be absent in some environments, // vAPI.warSecret() is optional, it could be absent in some environments,
@ -70,15 +74,19 @@ const warSecret = typeof vAPI === 'object' && vAPI !== null
? vAPI.warSecret ? vAPI.warSecret
: ( ) => ''; : ( ) => '';
const RESOURCES_SELFIE_VERSION = 7;
const RESOURCES_SELFIE_NAME = 'compiled/redirectEngine/resources';
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
const RedirectEntry = class { class RedirectEntry {
constructor() { constructor() {
this.mime = ''; this.mime = '';
this.data = ''; this.data = '';
this.warURL = undefined; this.warURL = undefined;
this.params = undefined; this.params = undefined;
this.dependencies = [];
} }
// Prevent redirection to web accessible resources when the request is // Prevent redirection to web accessible resources when the request is
@ -116,7 +124,7 @@ const RedirectEntry = class {
// https://github.com/uBlockOrigin/uBlock-issues/issues/701 // https://github.com/uBlockOrigin/uBlock-issues/issues/701
if ( this.data === '' ) { if ( this.data === '' ) {
const mime = typeToMimeMap.get(fctxt.type); const mime = typeToMimeMap.get(fctxt.type);
if ( mime === undefined ) { return; } if ( mime === '' ) { return; }
return `data:${mime},`; return `data:${mime},`;
} }
if ( this.data.startsWith('data:') === false ) { if ( this.data.startsWith('data:') === false ) {
@ -141,10 +149,11 @@ const RedirectEntry = class {
return this.data; return this.data;
} }
static fromContent(mime, content) { static fromContent(mime, content, dependencies = []) {
const r = new RedirectEntry(); const r = new RedirectEntry();
r.mime = mime; r.mime = mime;
r.data = content; r.data = content;
r.dependencies.push(...dependencies);
return r; return r;
} }
@ -154,93 +163,79 @@ const RedirectEntry = class {
r.data = selfie.data; r.data = selfie.data;
r.warURL = selfie.warURL; r.warURL = selfie.warURL;
r.params = selfie.params; r.params = selfie.params;
r.dependencies = selfie.dependencies || [];
return r; return r;
} }
}; }
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
const RedirectEngine = function() { class RedirectEngine {
constructor() {
this.aliases = new Map(); this.aliases = new Map();
this.resources = new Map(); this.resources = new Map();
this.reset(); this.reset();
this.modifyTime = Date.now(); this.modifyTime = Date.now();
this.resourceNameRegister = ''; this.resourceNameRegister = '';
}; }
/******************************************************************************/ reset() {
}
RedirectEngine.prototype.reset = function() { freeze() {
}; }
/******************************************************************************/ tokenToURL(
RedirectEngine.prototype.freeze = function() {
};
/******************************************************************************/
RedirectEngine.prototype.tokenToURL = function(
fctxt, fctxt,
token, token,
asDataURI = false asDataURI = false
) { ) {
const entry = this.resources.get(this.aliases.get(token) || token); const entry = this.resources.get(this.aliases.get(token) || token);
if ( entry === undefined ) { return; } if ( entry === undefined ) { return; }
this.resourceNameRegister = token; this.resourceNameRegister = token;
return entry.toURL(fctxt, asDataURI); return entry.toURL(fctxt, asDataURI);
}; }
/******************************************************************************/ tokenToDNR(token) {
RedirectEngine.prototype.tokenToDNR = function(token) {
const entry = this.resources.get(this.aliases.get(token) || token); const entry = this.resources.get(this.aliases.get(token) || token);
if ( entry === undefined ) { return; } if ( entry === undefined ) { return; }
if ( entry.warURL === undefined ) { return; } if ( entry.warURL === undefined ) { return; }
return entry.warURL; return entry.warURL;
}; }
/******************************************************************************/ hasToken(token) {
RedirectEngine.prototype.hasToken = function(token) {
if ( token === 'none' ) { return true; } if ( token === 'none' ) { return true; }
const asDataURI = token.charCodeAt(0) === 0x25 /* '%' */; const asDataURI = token.charCodeAt(0) === 0x25 /* '%' */;
if ( asDataURI ) { if ( asDataURI ) {
token = token.slice(1); token = token.slice(1);
} }
return this.resources.get(this.aliases.get(token) || token) !== undefined; return this.resources.get(this.aliases.get(token) || token) !== undefined;
}; }
/******************************************************************************/ async toSelfie() {
}
RedirectEngine.prototype.toSelfie = async function() { async fromSelfie() {
};
/******************************************************************************/
RedirectEngine.prototype.fromSelfie = async function() {
return true; return true;
}; }
/******************************************************************************/ contentFromName(name, mime = '') {
RedirectEngine.prototype.resourceContentFromName = function(name, mime) {
const entry = this.resources.get(this.aliases.get(name) || name); const entry = this.resources.get(this.aliases.get(name) || name);
if ( entry === undefined ) { return; } if ( entry === undefined ) { return; }
if ( mime === undefined || entry.mime.startsWith(mime) ) { if ( entry.mime.startsWith(mime) === false ) { return; }
return entry.toContent(); return {
js: entry.toContent(),
dependencies: entry.dependencies.slice(),
};
} }
};
/******************************************************************************/ // https://github.com/uBlockOrigin/uAssets/commit/deefe8755511
// Consider 'none' a reserved keyword, to be used to disable redirection.
// https://github.com/uBlockOrigin/uBlock-issues/issues/1419
// Append newlines to raw text to ensure processing of trailing resource.
// https://github.com/uBlockOrigin/uAssets/commit/deefe875551197d655f79cb540e62dfc17c95f42 resourcesFromString(text) {
// Consider 'none' a reserved keyword, to be used to disable redirection.
// https://github.com/uBlockOrigin/uBlock-issues/issues/1419
// Append newlines to raw text to ensure processing of trailing resource.
RedirectEngine.prototype.resourcesFromString = function(text) {
const lineIter = new LineIterator( const lineIter = new LineIterator(
removeTopCommentBlock(text) + '\n\n' removeTopCommentBlock(text) + '\n\n'
); );
@ -296,10 +291,7 @@ RedirectEngine.prototype.resourcesFromString = function(text) {
const content = orphanizeString( const content = orphanizeString(
fields.slice(2).join(encoded ? '' : '\n') fields.slice(2).join(encoded ? '' : '\n')
); );
this.resources.set( this.resources.set(name, RedirectEntry.fromContent(mime, content));
name,
RedirectEntry.fromContent(mime, content)
);
if ( Array.isArray(details) ) { if ( Array.isArray(details) ) {
for ( const { prop, value } of details ) { for ( const { prop, value } of details ) {
if ( prop !== 'alias' ) { continue; } if ( prop !== 'alias' ) { continue; }
@ -312,15 +304,9 @@ RedirectEngine.prototype.resourcesFromString = function(text) {
} }
this.modifyTime = Date.now(); this.modifyTime = Date.now();
}; }
const removeTopCommentBlock = function(text) { loadBuiltinResources(fetcher) {
return text.replace(/^\/\*[\S\s]+?\n\*\/\s*/, '');
};
/******************************************************************************/
RedirectEngine.prototype.loadBuiltinResources = function(fetcher) {
this.resources = new Map(); this.resources = new Map();
this.aliases = new Map(); this.aliases = new Map();
@ -330,7 +316,8 @@ RedirectEngine.prototype.loadBuiltinResources = function(fetcher) {
const { name, aliases, fn } = scriptlet; const { name, aliases, fn } = scriptlet;
const entry = RedirectEntry.fromContent( const entry = RedirectEntry.fromContent(
mimeFromName(name), mimeFromName(name),
fn.toString() fn.toString(),
scriptlet.dependencies,
); );
this.resources.set(name, entry); this.resources.set(name, entry);
if ( Array.isArray(aliases) === false ) { continue; } if ( Array.isArray(aliases) === false ) { continue; }
@ -403,11 +390,9 @@ RedirectEngine.prototype.loadBuiltinResources = function(fetcher) {
} }
return Promise.all(fetches); return Promise.all(fetches);
}; }
/******************************************************************************/ getResourceDetails() {
RedirectEngine.prototype.getResourceDetails = function() {
const out = new Map([ const out = new Map([
[ 'none', { canInject: false, canRedirect: true, aliasOf: '' } ], [ 'none', { canInject: false, canRedirect: true, aliasOf: '' } ],
]); ]);
@ -429,14 +414,9 @@ RedirectEngine.prototype.getResourceDetails = function() {
return Array.from(out).sort((a, b) => { return Array.from(out).sort((a, b) => {
return a[0].localeCompare(b[0]); return a[0].localeCompare(b[0]);
}); });
}; }
/******************************************************************************/ selfieFromResources(storage) {
const RESOURCES_SELFIE_VERSION = 7;
const RESOURCES_SELFIE_NAME = 'compiled/redirectEngine/resources';
RedirectEngine.prototype.selfieFromResources = function(storage) {
storage.put( storage.put(
RESOURCES_SELFIE_NAME, RESOURCES_SELFIE_NAME,
JSON.stringify({ JSON.stringify({
@ -445,9 +425,9 @@ RedirectEngine.prototype.selfieFromResources = function(storage) {
resources: Array.from(this.resources), resources: Array.from(this.resources),
}) })
); );
}; }
RedirectEngine.prototype.resourcesFromSelfie = async function(storage) { async resourcesFromSelfie(storage) {
const result = await storage.get(RESOURCES_SELFIE_NAME); const result = await storage.get(RESOURCES_SELFIE_NAME);
let selfie; let selfie;
try { try {
@ -467,11 +447,12 @@ RedirectEngine.prototype.resourcesFromSelfie = async function(storage) {
this.resources.set(token, RedirectEntry.fromSelfie(entry)); this.resources.set(token, RedirectEntry.fromSelfie(entry));
} }
return true; return true;
}; }
RedirectEngine.prototype.invalidateResourcesSelfie = function(storage) { invalidateResourcesSelfie(storage) {
storage.remove(RESOURCES_SELFIE_NAME); storage.remove(RESOURCES_SELFIE_NAME);
}; }
}
/******************************************************************************/ /******************************************************************************/

View file

@ -141,13 +141,8 @@ const normalizeRawFilter = function(parser) {
return `+js(${args.join(', ')})`; return `+js(${args.join(', ')})`;
}; };
const lookupScriptlet = function(rawToken, reng, toInject) { const lookupScriptlet = function(rawToken, scriptletMap, dependencyMap) {
if ( toInject.has(rawToken) ) { return; } if ( scriptletMap.has(rawToken) ) { return; }
if ( scriptletCache.resetTime < reng.modifyTime ) {
scriptletCache.reset();
}
let content = scriptletCache.lookup(rawToken);
if ( content === undefined ) {
const pos = rawToken.indexOf(','); const pos = rawToken.indexOf(',');
let token, args = ''; let token, args = '';
if ( pos === -1 ) { if ( pos === -1 ) {
@ -158,21 +153,28 @@ const lookupScriptlet = function(rawToken, reng, toInject) {
} }
// TODO: The alias lookup can be removed once scriptlet resources // TODO: The alias lookup can be removed once scriptlet resources
// with obsolete name are converted to their new name. // with obsolete name are converted to their new name.
if ( reng.aliases.has(token) ) { if ( redirectEngine.aliases.has(token) ) {
token = reng.aliases.get(token); token = redirectEngine.aliases.get(token);
} else { } else {
token = `${token}.js`; token = `${token}.js`;
} }
content = reng.resourceContentFromName(token, 'text/javascript'); const details = redirectEngine.contentFromName(token, 'text/javascript');
if ( !content ) { return; } if ( details === undefined ) { return; }
content = patchScriptlet(content, args); const content = patchScriptlet(details.js, args);
content = const dependencies = details.dependencies || [];
'try {\n' + while ( dependencies.length !== 0 ) {
content + '\n' + const token = dependencies.shift();
'} catch ( e ) { }'; if ( dependencyMap.has(token) ) { continue; }
scriptletCache.add(rawToken, content); const details = redirectEngine.contentFromName(token, 'fn/javascript');
if ( details === undefined ) { continue; }
dependencyMap.set(token, details.js);
if ( Array.isArray(details.dependencies) === false ) { continue; }
dependencies.push(...details.dependencies);
} }
toInject.set(rawToken, content); scriptletMap.set(
rawToken,
[ 'try {', content, '} catch (e) {', '}' ].join('\n')
);
}; };
// Fill-in scriptlet argument placeholders. // Fill-in scriptlet argument placeholders.
@ -183,8 +185,10 @@ const patchScriptlet = function(content, args) {
if ( args.startsWith('{') && args.endsWith('}') ) { if ( args.startsWith('{') && args.endsWith('}') ) {
return content.replace('{{args}}', args); return content.replace('{{args}}', args);
} }
if ( args === '' ) {
return content.replace('{{args}}', '');
}
const arglist = []; const arglist = [];
if ( args !== '' ) {
let s = args; let s = args;
let len = s.length; let len = s.length;
let beg = 0, pos = 0; let beg = 0, pos = 0;
@ -202,12 +206,10 @@ const patchScriptlet = function(content, args) {
beg = pos = pos + 1; beg = pos = pos + 1;
i++; i++;
} }
}
for ( let i = 0; i < arglist.length; i++ ) { for ( let i = 0; i < arglist.length; i++ ) {
content = content.replace(`{{${i+1}}}`, arglist[i]); content = content.replace(`{{${i+1}}}`, arglist[i]);
} }
content = content.replace('{{args}}', arglist.map(a => `'${a}'`).join(', ')); return content.replace('{{args}}', arglist.map(a => `'${a}'`).join(', '));
return content;
}; };
const logOne = function(tabId, url, filter) { const logOne = function(tabId, url, filter) {
@ -225,6 +227,7 @@ const logOne = function(tabId, url, filter) {
scriptletFilteringEngine.reset = function() { scriptletFilteringEngine.reset = function() {
scriptletDB.clear(); scriptletDB.clear();
duplicates.clear(); duplicates.clear();
scriptletCache.reset();
acceptedCount = 0; acceptedCount = 0;
discardedCount = 0; discardedCount = 0;
}; };
@ -232,6 +235,7 @@ scriptletFilteringEngine.reset = function() {
scriptletFilteringEngine.freeze = function() { scriptletFilteringEngine.freeze = function() {
duplicates.clear(); duplicates.clear();
scriptletDB.collectGarbage(); scriptletDB.collectGarbage();
scriptletCache.reset();
}; };
scriptletFilteringEngine.compile = function(parser, writer) { scriptletFilteringEngine.compile = function(parser, writer) {
@ -292,7 +296,8 @@ scriptletFilteringEngine.fromCompiledContent = function(reader) {
const $scriptlets = new Set(); const $scriptlets = new Set();
const $exceptions = new Set(); const $exceptions = new Set();
const $scriptletToCodeMap = new Map(); const $scriptletMap = new Map();
const $scriptletDependencyMap = new Map();
scriptletFilteringEngine.retrieve = function(request, options = {}) { scriptletFilteringEngine.retrieve = function(request, options = {}) {
if ( scriptletDB.size === 0 ) { return; } if ( scriptletDB.size === 0 ) { return; }
@ -328,21 +333,39 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) {
return; return;
} }
$scriptletToCodeMap.clear(); if ( scriptletCache.resetTime < redirectEngine.modifyTime ) {
for ( const token of $scriptlets ) { scriptletCache.reset();
lookupScriptlet(token, redirectEngine, $scriptletToCodeMap);
} }
if ( $scriptletToCodeMap.size === 0 ) { return; }
// Return an array of scriptlets, and log results if needed. let cacheDetails = scriptletCache.lookup(hostname);
const out = []; if ( cacheDetails === undefined ) {
for ( const [ token, code ] of $scriptletToCodeMap ) { const fullCode = [];
for ( const token of $scriptlets ) {
if ( $exceptions.has(token) ) { continue; }
lookupScriptlet(token, $scriptletMap, $scriptletDependencyMap);
}
for ( const token of $scriptlets ) {
const isException = $exceptions.has(token); const isException = $exceptions.has(token);
if ( isException === false ) { if ( isException === false ) {
out.push(code); fullCode.push($scriptletMap.get(token));
} }
if ( mustLog === false ) { continue; } }
if ( isException ) { for ( const code of $scriptletDependencyMap.values() ) {
fullCode.push(code);
}
cacheDetails = {
code: fullCode.join('\n'),
tokens: Array.from($scriptlets),
exceptions: Array.from($exceptions),
};
scriptletCache.add(hostname, cacheDetails);
$scriptletMap.clear();
$scriptletDependencyMap.clear();
}
if ( mustLog ) {
for ( const token of cacheDetails.tokens ) {
if ( cacheDetails.exceptions.includes(token) ) {
logOne(request.tabId, request.url, `#@#+js(${token})`); logOne(request.tabId, request.url, `#@#+js(${token})`);
} else { } else {
options.logEntries.push({ options.logEntries.push({
@ -352,16 +375,16 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) {
}); });
} }
} }
}
if ( out.length === 0 ) { return; } if ( cacheDetails.code === '' ) { return; }
const out = [ cacheDetails.code ];
if ( µb.hiddenSettings.debugScriptlets ) { if ( µb.hiddenSettings.debugScriptlets ) {
out.unshift('debugger;'); out.unshift('debugger;');
} }
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
// Provide a private Map() object available for use by all
// scriptlets.
out.unshift( out.unshift(
'(function() {', '(function() {',
'// >>>> start of private namespace', '// >>>> start of private namespace',