2019-07-06 18:36:28 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
2023-12-04 18:10:34 +01:00
|
|
|
|
uBlock Origin - a comprehensive, efficient content blocker
|
2019-07-06 18:36:28 +02:00
|
|
|
|
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.
|
|
|
|
|
*/
|
|
|
|
|
|
2024-03-20 14:31:17 +01:00
|
|
|
|
/* eslint no-prototype-builtins: 0 */
|
|
|
|
|
|
2023-03-26 18:31:36 +02:00
|
|
|
|
// Externally added to the private namespace in which scriptlets execute.
|
2023-03-31 02:46:44 +02:00
|
|
|
|
/* global scriptletGlobals */
|
2023-03-26 18:31:36 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
export const builtinScriptlets = [];
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
|
|
Helper functions
|
|
|
|
|
|
|
|
|
|
These are meant to be used as dependencies to injectable scriptlets.
|
|
|
|
|
|
|
|
|
|
*******************************************************************************/
|
|
|
|
|
|
2023-03-26 18:31:36 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'safe-self.fn',
|
|
|
|
|
fn: safeSelf,
|
|
|
|
|
});
|
|
|
|
|
function safeSelf() {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( scriptletGlobals.safeSelf ) {
|
|
|
|
|
return scriptletGlobals.safeSelf;
|
2023-03-26 18:31:36 +02:00
|
|
|
|
}
|
2023-08-25 13:28:50 +02:00
|
|
|
|
const self = globalThis;
|
2023-03-26 18:31:36 +02:00
|
|
|
|
const safe = {
|
2023-10-14 14:03:29 +02:00
|
|
|
|
'Array_from': Array.from,
|
2023-07-27 15:41:56 +02:00
|
|
|
|
'Error': self.Error,
|
2023-11-06 15:10:21 +01:00
|
|
|
|
'Function_toStringFn': self.Function.prototype.toString,
|
|
|
|
|
'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg),
|
2023-10-09 15:45:26 +02:00
|
|
|
|
'Math_floor': Math.floor,
|
2023-11-25 17:13:57 +01:00
|
|
|
|
'Math_max': Math.max,
|
|
|
|
|
'Math_min': Math.min,
|
2023-10-09 15:45:26 +02:00
|
|
|
|
'Math_random': Math.random,
|
2024-01-11 17:41:37 +01:00
|
|
|
|
'Object': Object,
|
2023-06-17 17:53:08 +02:00
|
|
|
|
'Object_defineProperty': Object.defineProperty.bind(Object),
|
2024-01-11 17:41:37 +01:00
|
|
|
|
'Object_fromEntries': Object.fromEntries.bind(Object),
|
|
|
|
|
'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object),
|
2023-03-26 18:31:36 +02:00
|
|
|
|
'RegExp': self.RegExp,
|
|
|
|
|
'RegExp_test': self.RegExp.prototype.test,
|
|
|
|
|
'RegExp_exec': self.RegExp.prototype.exec,
|
2023-10-07 17:44:18 +02:00
|
|
|
|
'Request_clone': self.Request.prototype.clone,
|
2023-08-20 01:21:22 +02:00
|
|
|
|
'XMLHttpRequest': self.XMLHttpRequest,
|
2023-04-28 13:58:23 +02:00
|
|
|
|
'addEventListener': self.EventTarget.prototype.addEventListener,
|
|
|
|
|
'removeEventListener': self.EventTarget.prototype.removeEventListener,
|
2023-08-05 17:55:47 +02:00
|
|
|
|
'fetch': self.fetch,
|
2023-10-29 15:22:54 +01:00
|
|
|
|
'JSON': self.JSON,
|
|
|
|
|
'JSON_parseFn': self.JSON.parse,
|
|
|
|
|
'JSON_stringifyFn': self.JSON.stringify,
|
|
|
|
|
'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args),
|
|
|
|
|
'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args),
|
2023-04-15 17:14:14 +02:00
|
|
|
|
'log': console.log.bind(console),
|
2024-01-25 18:20:38 +01:00
|
|
|
|
// Properties
|
|
|
|
|
logLevel: 0,
|
|
|
|
|
// Methods
|
|
|
|
|
makeLogPrefix(...args) {
|
|
|
|
|
return this.sendToLogger && `[${args.join(' \u205D ')}]` || '';
|
|
|
|
|
},
|
2023-08-08 13:41:21 +02:00
|
|
|
|
uboLog(...args) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( this.sendToLogger === undefined ) { return; }
|
|
|
|
|
if ( args === undefined || args[0] === '' ) { return; }
|
|
|
|
|
return this.sendToLogger('info', ...args);
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
uboErr(...args) {
|
|
|
|
|
if ( this.sendToLogger === undefined ) { return; }
|
|
|
|
|
if ( args === undefined || args[0] === '' ) { return; }
|
|
|
|
|
return this.sendToLogger('error', ...args);
|
2023-04-02 18:01:58 +02:00
|
|
|
|
},
|
2024-01-20 16:33:36 +01:00
|
|
|
|
escapeRegexChars(s) {
|
|
|
|
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
|
},
|
2023-08-08 13:41:21 +02:00
|
|
|
|
initPattern(pattern, options = {}) {
|
2023-07-31 15:38:04 +02:00
|
|
|
|
if ( pattern === '' ) {
|
|
|
|
|
return { matchAll: true };
|
|
|
|
|
}
|
2023-09-28 17:26:45 +02:00
|
|
|
|
const expect = (options.canNegate !== true || pattern.startsWith('!') === false);
|
2023-07-31 15:38:04 +02:00
|
|
|
|
if ( expect === false ) {
|
|
|
|
|
pattern = pattern.slice(1);
|
|
|
|
|
}
|
|
|
|
|
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
|
|
|
|
|
if ( match !== null ) {
|
|
|
|
|
return {
|
|
|
|
|
re: new this.RegExp(
|
|
|
|
|
match[1],
|
|
|
|
|
match[2] || options.flags
|
|
|
|
|
),
|
|
|
|
|
expect,
|
|
|
|
|
};
|
|
|
|
|
}
|
2023-12-06 16:17:19 +01:00
|
|
|
|
if ( options.flags !== undefined ) {
|
|
|
|
|
return {
|
2024-01-20 16:33:36 +01:00
|
|
|
|
re: new this.RegExp(this.escapeRegexChars(pattern),
|
2023-12-06 16:17:19 +01:00
|
|
|
|
options.flags
|
|
|
|
|
),
|
|
|
|
|
expect,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { pattern, expect };
|
2023-07-31 15:38:04 +02:00
|
|
|
|
},
|
2023-08-08 13:41:21 +02:00
|
|
|
|
testPattern(details, haystack) {
|
2023-07-31 15:38:04 +02:00
|
|
|
|
if ( details.matchAll ) { return true; }
|
2023-12-06 16:17:19 +01:00
|
|
|
|
if ( details.re ) {
|
|
|
|
|
return this.RegExp_test.call(details.re, haystack) === details.expect;
|
|
|
|
|
}
|
|
|
|
|
return haystack.includes(details.pattern) === details.expect;
|
2023-07-31 15:38:04 +02:00
|
|
|
|
},
|
2023-10-14 03:51:13 +02:00
|
|
|
|
patternToRegex(pattern, flags = undefined, verbatim = false) {
|
2023-08-08 13:41:21 +02:00
|
|
|
|
if ( pattern === '' ) { return /^/; }
|
|
|
|
|
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
|
|
|
|
|
if ( match === null ) {
|
2024-01-20 16:33:36 +01:00
|
|
|
|
const reStr = this.escapeRegexChars(pattern);
|
2023-10-21 02:10:35 +02:00
|
|
|
|
return new RegExp(verbatim ? `^${reStr}$` : reStr, flags);
|
2023-08-08 13:41:21 +02:00
|
|
|
|
}
|
|
|
|
|
try {
|
2023-11-10 18:29:51 +01:00
|
|
|
|
return new RegExp(match[1], match[2] || undefined);
|
2023-08-08 13:41:21 +02:00
|
|
|
|
}
|
|
|
|
|
catch(ex) {
|
|
|
|
|
}
|
|
|
|
|
return /^/;
|
|
|
|
|
},
|
|
|
|
|
getExtraArgs(args, offset = 0) {
|
|
|
|
|
const entries = args.slice(offset).reduce((out, v, i, a) => {
|
|
|
|
|
if ( (i & 1) === 0 ) {
|
|
|
|
|
const rawValue = a[i+1];
|
|
|
|
|
const value = /^\d+$/.test(rawValue)
|
|
|
|
|
? parseInt(rawValue, 10)
|
|
|
|
|
: rawValue;
|
|
|
|
|
out.push([ a[i], value ]);
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}, []);
|
2024-01-11 17:41:37 +01:00
|
|
|
|
return this.Object_fromEntries(entries);
|
2023-08-08 13:41:21 +02:00
|
|
|
|
},
|
2023-03-26 18:31:36 +02:00
|
|
|
|
};
|
2024-01-25 18:20:38 +01:00
|
|
|
|
scriptletGlobals.safeSelf = safe;
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
if ( scriptletGlobals.bcSecret === undefined ) { return safe; }
|
|
|
|
|
// This is executed only when the logger is opened
|
|
|
|
|
const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret);
|
|
|
|
|
let bcBuffer = [];
|
|
|
|
|
safe.logLevel = scriptletGlobals.logLevel || 1;
|
|
|
|
|
safe.sendToLogger = (type, ...args) => {
|
|
|
|
|
if ( args.length === 0 ) { return; }
|
|
|
|
|
const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`;
|
|
|
|
|
if ( bcBuffer === undefined ) {
|
|
|
|
|
return bc.postMessage({ what: 'messageToLogger', type, text });
|
|
|
|
|
}
|
|
|
|
|
bcBuffer.push({ type, text });
|
|
|
|
|
};
|
|
|
|
|
bc.onmessage = ev => {
|
|
|
|
|
const msg = ev.data;
|
|
|
|
|
switch ( msg ) {
|
|
|
|
|
case 'iamready!':
|
|
|
|
|
if ( bcBuffer === undefined ) { break; }
|
|
|
|
|
bcBuffer.forEach(({ type, text }) =>
|
|
|
|
|
bc.postMessage({ what: 'messageToLogger', type, text })
|
|
|
|
|
);
|
|
|
|
|
bcBuffer = undefined;
|
|
|
|
|
break;
|
|
|
|
|
case 'setScriptletLogLevelToOne':
|
|
|
|
|
safe.logLevel = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 'setScriptletLogLevelToTwo':
|
|
|
|
|
safe.logLevel = 2;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
bc.postMessage('areyouready?');
|
2023-03-26 18:31:36 +02:00
|
|
|
|
return safe;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'get-exception-token.fn',
|
|
|
|
|
fn: getExceptionToken,
|
2023-10-09 15:45:26 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
2023-03-26 15:13:17 +02:00
|
|
|
|
});
|
|
|
|
|
function getExceptionToken() {
|
2023-10-09 15:45:26 +02:00
|
|
|
|
const safe = safeSelf();
|
2023-03-26 15:13:17 +02:00
|
|
|
|
const token =
|
|
|
|
|
String.fromCharCode(Date.now() % 26 + 97) +
|
2023-10-09 15:45:26 +02:00
|
|
|
|
safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36);
|
2023-03-26 15:13:17 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-02 18:01:58 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'should-debug.fn',
|
|
|
|
|
fn: shouldDebug,
|
|
|
|
|
});
|
|
|
|
|
function shouldDebug(details) {
|
|
|
|
|
if ( details instanceof Object === false ) { return false; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
return scriptletGlobals.canDebug && details.debug;
|
2023-04-02 18:01:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 18:52:17 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'run-at.fn',
|
|
|
|
|
fn: runAt,
|
2023-04-28 13:58:23 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
2023-04-27 18:52:17 +02:00
|
|
|
|
});
|
|
|
|
|
function runAt(fn, when) {
|
|
|
|
|
const intFromReadyState = state => {
|
2023-05-19 18:55:01 +02:00
|
|
|
|
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;
|
2023-04-27 18:52:17 +02:00
|
|
|
|
};
|
|
|
|
|
const runAt = intFromReadyState(when);
|
|
|
|
|
if ( intFromReadyState(document.readyState) >= runAt ) {
|
|
|
|
|
fn(); return;
|
|
|
|
|
}
|
|
|
|
|
const onStateChange = ( ) => {
|
|
|
|
|
if ( intFromReadyState(document.readyState) < runAt ) { return; }
|
|
|
|
|
fn();
|
2023-04-28 13:58:23 +02:00
|
|
|
|
safe.removeEventListener.apply(document, args);
|
2023-04-27 18:52:17 +02:00
|
|
|
|
};
|
2023-04-28 13:58:23 +02:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const args = [ 'readystatechange', onStateChange, { capture: true } ];
|
|
|
|
|
safe.addEventListener.apply(document, args);
|
2023-04-27 18:52:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 16:59:27 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-05-24 16:32:03 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'run-at-html-element.fn',
|
2023-10-15 17:08:15 +02:00
|
|
|
|
fn: runAtHtmlElementFn,
|
2023-05-24 16:32:03 +02:00
|
|
|
|
});
|
2023-10-15 17:08:15 +02:00
|
|
|
|
function runAtHtmlElementFn(fn) {
|
2023-05-24 16:32:03 +02:00
|
|
|
|
if ( document.documentElement ) {
|
|
|
|
|
fn();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const observer = new MutationObserver(( ) => {
|
|
|
|
|
observer.disconnect();
|
2023-05-24 21:33:46 +02:00
|
|
|
|
fn();
|
2023-05-24 16:32:03 +02:00
|
|
|
|
});
|
|
|
|
|
observer.observe(document, { childList: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-11-25 17:13:57 +01:00
|
|
|
|
// Reference:
|
|
|
|
|
// https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#prevent-xhr
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'generate-content.fn',
|
|
|
|
|
fn: generateContentFn,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function generateContentFn(directive) {
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const randomize = len => {
|
|
|
|
|
const chunks = [];
|
|
|
|
|
let textSize = 0;
|
|
|
|
|
do {
|
|
|
|
|
const s = safe.Math_random().toString(36).slice(2);
|
|
|
|
|
chunks.push(s);
|
|
|
|
|
textSize += s.length;
|
|
|
|
|
}
|
|
|
|
|
while ( textSize < len );
|
|
|
|
|
return chunks.join(' ').slice(0, len);
|
|
|
|
|
};
|
|
|
|
|
if ( directive === 'true' ) {
|
|
|
|
|
return Promise.resolve(randomize(10));
|
|
|
|
|
}
|
2024-01-01 16:24:47 +01:00
|
|
|
|
if ( directive === 'emptyObj' ) {
|
|
|
|
|
return Promise.resolve('{}');
|
|
|
|
|
}
|
|
|
|
|
if ( directive === 'emptyArr' ) {
|
|
|
|
|
return Promise.resolve('[]');
|
|
|
|
|
}
|
|
|
|
|
if ( directive === 'emptyStr' ) {
|
|
|
|
|
return Promise.resolve('');
|
|
|
|
|
}
|
2023-11-25 17:13:57 +01:00
|
|
|
|
if ( directive.startsWith('length:') ) {
|
|
|
|
|
const match = /^length:(\d+)(?:-(\d+))?$/.exec(directive);
|
|
|
|
|
if ( match ) {
|
|
|
|
|
const min = parseInt(match[1], 10);
|
|
|
|
|
const extent = safe.Math_max(parseInt(match[2], 10) || 0, min) - min;
|
|
|
|
|
const len = safe.Math_min(min + extent * safe.Math_random(), 500000);
|
|
|
|
|
return Promise.resolve(randomize(len | 0));
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( directive.startsWith('war:') && scriptletGlobals.warOrigin ) {
|
2023-11-25 17:13:57 +01:00
|
|
|
|
return new Promise(resolve => {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const warOrigin = scriptletGlobals.warOrigin;
|
2023-11-25 17:13:57 +01:00
|
|
|
|
const warName = directive.slice(4);
|
|
|
|
|
const fullpath = [ warOrigin, '/', warName ];
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const warSecret = scriptletGlobals.warSecret;
|
2023-11-25 17:13:57 +01:00
|
|
|
|
if ( warSecret !== undefined ) {
|
|
|
|
|
fullpath.push('?secret=', warSecret);
|
|
|
|
|
}
|
|
|
|
|
const warXHR = new safe.XMLHttpRequest();
|
|
|
|
|
warXHR.responseType = 'text';
|
|
|
|
|
warXHR.onloadend = ev => {
|
|
|
|
|
resolve(ev.target.responseText || '');
|
|
|
|
|
};
|
|
|
|
|
warXHR.open('GET', fullpath.join(''));
|
|
|
|
|
warXHR.send();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return Promise.resolve('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-05-24 16:32:03 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'abort-current-script-core.fn',
|
|
|
|
|
fn: abortCurrentScriptCore,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'get-exception-token.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
'should-debug.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
// Issues to mind before changing anything:
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/2154
|
|
|
|
|
function abortCurrentScriptCore(
|
2023-07-25 15:22:47 +02:00
|
|
|
|
target = '',
|
|
|
|
|
needle = '',
|
|
|
|
|
context = ''
|
2023-05-24 16:32:03 +02:00
|
|
|
|
) {
|
|
|
|
|
if ( typeof target !== 'string' ) { return; }
|
|
|
|
|
if ( target === '' ) { return; }
|
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('abort-current-script', target, needle, context);
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const reNeedle = safe.patternToRegex(needle);
|
|
|
|
|
const reContext = safe.patternToRegex(context);
|
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
2023-05-24 16:32:03 +02:00
|
|
|
|
const thisScript = document.currentScript;
|
|
|
|
|
const chain = target.split('.');
|
|
|
|
|
let owner = window;
|
|
|
|
|
let prop;
|
|
|
|
|
for (;;) {
|
|
|
|
|
prop = chain.shift();
|
|
|
|
|
if ( chain.length === 0 ) { break; }
|
2023-07-05 16:00:31 +02:00
|
|
|
|
if ( prop in owner === false ) { break; }
|
2023-05-24 16:32:03 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
2023-07-25 15:22:47 +02:00
|
|
|
|
const debug = shouldDebug(extraArgs);
|
2023-05-24 16:32:03 +02:00
|
|
|
|
const exceptionToken = getExceptionToken();
|
|
|
|
|
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 ( e === thisScript ) { return; }
|
2023-08-20 01:21:22 +02:00
|
|
|
|
if ( context !== '' && reContext.test(e.src) === false ) {
|
2024-03-20 14:31:17 +01:00
|
|
|
|
// eslint-disable-next-line no-debugger
|
|
|
|
|
if ( debug === 'nomatch' || debug === 'all' ) { debugger; }
|
2023-08-20 01:21:22 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2024-01-27 12:43:36 +01:00
|
|
|
|
if ( safe.logLevel > 1 && context !== '' ) {
|
2024-01-27 04:38:31 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Matched src\n${e.src}`);
|
2024-01-25 18:20:38 +01:00
|
|
|
|
}
|
2023-05-24 16:32:03 +02:00
|
|
|
|
const scriptText = getScriptText(e);
|
2023-08-20 01:21:22 +02:00
|
|
|
|
if ( reNeedle.test(scriptText) === false ) {
|
2024-03-20 14:31:17 +01:00
|
|
|
|
// eslint-disable-next-line no-debugger
|
|
|
|
|
if ( debug === 'nomatch' || debug === 'all' ) { debugger; }
|
2023-08-20 01:21:22 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( safe.logLevel > 1 ) {
|
2024-01-27 04:38:31 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Matched text\n${scriptText}`);
|
2024-01-25 18:20:38 +01:00
|
|
|
|
}
|
2024-03-20 14:31:17 +01:00
|
|
|
|
// eslint-disable-next-line no-debugger
|
|
|
|
|
if ( debug === 'match' || debug === 'all' ) { debugger; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'Aborted');
|
2023-05-24 16:32:03 +02:00
|
|
|
|
throw new ReferenceError(exceptionToken);
|
|
|
|
|
};
|
2024-03-20 14:31:17 +01:00
|
|
|
|
// eslint-disable-next-line no-debugger
|
|
|
|
|
if ( debug === 'install' ) { debugger; }
|
2023-05-24 16:32:03 +02:00
|
|
|
|
try {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch(ex) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboErr(logPrefix, `Error: ${ex}`);
|
2023-05-24 16:32:03 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-05-23 16:59:27 +02:00
|
|
|
|
builtinScriptlets.push({
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
name: 'validate-constant.fn',
|
|
|
|
|
fn: validateConstantFn,
|
2023-05-23 16:59:27 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
function validateConstantFn(trusted, raw) {
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
|
|
|
|
|
let value;
|
|
|
|
|
if ( raw === 'undefined' ) {
|
|
|
|
|
value = undefined;
|
|
|
|
|
} else if ( raw === 'false' ) {
|
|
|
|
|
value = false;
|
|
|
|
|
} else if ( raw === 'true' ) {
|
|
|
|
|
value = true;
|
|
|
|
|
} else if ( raw === 'null' ) {
|
|
|
|
|
value = null;
|
|
|
|
|
} else if ( raw === "''" || raw === '' ) {
|
|
|
|
|
value = '';
|
|
|
|
|
} else if ( raw === '[]' || raw === 'emptyArr' ) {
|
|
|
|
|
value = [];
|
|
|
|
|
} else if ( raw === '{}' || raw === 'emptyObj' ) {
|
|
|
|
|
value = {};
|
|
|
|
|
} else if ( raw === 'noopFunc' ) {
|
|
|
|
|
value = function(){};
|
|
|
|
|
} else if ( raw === 'trueFunc' ) {
|
|
|
|
|
value = function(){ return true; };
|
|
|
|
|
} else if ( raw === 'falseFunc' ) {
|
|
|
|
|
value = function(){ return false; };
|
|
|
|
|
} else if ( /^-?\d+$/.test(raw) ) {
|
|
|
|
|
value = parseInt(raw);
|
|
|
|
|
if ( isNaN(raw) ) { return; }
|
|
|
|
|
if ( Math.abs(raw) > 0x7FFF ) { return; }
|
|
|
|
|
} else if ( trusted ) {
|
|
|
|
|
if ( raw.startsWith('{') && raw.endsWith('}') ) {
|
|
|
|
|
try { value = safe.JSON_parse(raw).value; } catch(ex) { return; }
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( extraArgs.as !== undefined ) {
|
|
|
|
|
if ( extraArgs.as === 'function' ) {
|
|
|
|
|
return ( ) => value;
|
|
|
|
|
} else if ( extraArgs.as === 'callback' ) {
|
|
|
|
|
return ( ) => (( ) => value);
|
|
|
|
|
} else if ( extraArgs.as === 'resolved' ) {
|
|
|
|
|
return Promise.resolve(value);
|
|
|
|
|
} else if ( extraArgs.as === 'rejected' ) {
|
|
|
|
|
return Promise.reject(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
2023-05-23 16:59:27 +02:00
|
|
|
|
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'set-constant.fn',
|
|
|
|
|
fn: setConstantFn,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'run-at.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
'validate-constant.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function setConstantFn(
|
2023-05-23 16:59:27 +02:00
|
|
|
|
trusted = false,
|
Re-factor extra args for `set-constant` scriptlet
To prepare for better compatibility with AdGuard's own `set-constant`
scriptlet.
The 3rd position parameter which dictates how to set the value has
been converted into a vararg paramater, as follow:
..., as, function
..., as, callback
..., as, resolved
..., as, rejected
Similarly, the parameter used to dictate when the scriptlet
should become effective is now to be used as a vararg:
..., runAt, load
Related issue:
https://github.com/uBlockOrigin/uBlock-issues/issues/2783
Ideally, AdGuard would support its `stack` parameter as a
vararg, to be discussed.
2023-08-22 16:12:08 +02:00
|
|
|
|
chain = '',
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
rawValue = ''
|
2023-05-23 16:59:27 +02:00
|
|
|
|
) {
|
|
|
|
|
if ( chain === '' ) { return; }
|
2023-06-17 17:53:08 +02:00
|
|
|
|
const safe = safeSelf();
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('set-constant', chain, rawValue);
|
Re-factor extra args for `set-constant` scriptlet
To prepare for better compatibility with AdGuard's own `set-constant`
scriptlet.
The 3rd position parameter which dictates how to set the value has
been converted into a vararg paramater, as follow:
..., as, function
..., as, callback
..., as, resolved
..., as, rejected
Similarly, the parameter used to dictate when the scriptlet
should become effective is now to be used as a vararg:
..., runAt, load
Related issue:
https://github.com/uBlockOrigin/uBlock-issues/issues/2783
Ideally, AdGuard would support its `stack` parameter as a
vararg, to be discussed.
2023-08-22 16:12:08 +02:00
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
function setConstant(chain, rawValue) {
|
2023-05-23 16:59:27 +02:00
|
|
|
|
const trappedProp = (( ) => {
|
|
|
|
|
const pos = chain.lastIndexOf('.');
|
|
|
|
|
if ( pos === -1 ) { return chain; }
|
|
|
|
|
return chain.slice(pos+1);
|
|
|
|
|
})();
|
|
|
|
|
const cloakFunc = fn => {
|
2023-06-17 17:53:08 +02:00
|
|
|
|
safe.Object_defineProperty(fn, 'name', { value: trappedProp });
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
return new Proxy(fn, {
|
2023-05-23 16:59:27 +02:00
|
|
|
|
defineProperty(target, prop) {
|
|
|
|
|
if ( prop !== 'toString' ) {
|
2023-06-17 17:53:08 +02:00
|
|
|
|
return Reflect.defineProperty(...arguments);
|
2023-05-23 16:59:27 +02:00
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
};
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
if ( trappedProp === '' ) { return; }
|
|
|
|
|
const thisScript = document.currentScript;
|
|
|
|
|
let normalValue = validateConstantFn(trusted, rawValue);
|
|
|
|
|
if ( rawValue === 'noopFunc' || rawValue === 'trueFunc' || rawValue === 'falseFunc' ) {
|
|
|
|
|
normalValue = cloakFunc(normalValue);
|
2023-05-23 16:59:27 +02:00
|
|
|
|
}
|
|
|
|
|
let aborted = false;
|
|
|
|
|
const mustAbort = function(v) {
|
|
|
|
|
if ( trusted ) { return false; }
|
|
|
|
|
if ( aborted ) { return true; }
|
|
|
|
|
aborted =
|
|
|
|
|
(v !== undefined && v !== null) &&
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
(normalValue !== undefined && normalValue !== null) &&
|
|
|
|
|
(typeof v !== typeof normalValue);
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( aborted ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Aborted because value set to ${v}`);
|
|
|
|
|
}
|
2023-05-23 16:59:27 +02:00
|
|
|
|
return aborted;
|
|
|
|
|
};
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
|
|
|
|
|
// Support multiple trappers for the same property.
|
|
|
|
|
const trapProp = function(owner, prop, configurable, handler) {
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
if ( handler.init(configurable ? owner[prop] : normalValue) === false ) { return; }
|
2024-01-11 17:41:37 +01:00
|
|
|
|
const odesc = safe.Object_getOwnPropertyDescriptor(owner, prop);
|
2023-05-23 16:59:27 +02:00
|
|
|
|
let prevGetter, prevSetter;
|
2024-01-11 17:41:37 +01:00
|
|
|
|
if ( odesc instanceof safe.Object ) {
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
owner[prop] = normalValue;
|
2023-05-23 16:59:27 +02:00
|
|
|
|
if ( odesc.get instanceof Function ) {
|
|
|
|
|
prevGetter = odesc.get;
|
|
|
|
|
}
|
|
|
|
|
if ( odesc.set instanceof Function ) {
|
|
|
|
|
prevSetter = odesc.set;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
try {
|
2023-06-17 17:53:08 +02:00
|
|
|
|
safe.Object_defineProperty(owner, prop, {
|
2023-05-23 16:59:27 +02:00
|
|
|
|
configurable,
|
|
|
|
|
get() {
|
|
|
|
|
if ( prevGetter !== undefined ) {
|
|
|
|
|
prevGetter();
|
|
|
|
|
}
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
return handler.getter();
|
2023-05-23 16:59:27 +02:00
|
|
|
|
},
|
|
|
|
|
set(a) {
|
|
|
|
|
if ( prevSetter !== undefined ) {
|
|
|
|
|
prevSetter(a);
|
|
|
|
|
}
|
|
|
|
|
handler.setter(a);
|
|
|
|
|
}
|
|
|
|
|
});
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'Trap installed');
|
2023-05-23 16:59:27 +02:00
|
|
|
|
} catch(ex) {
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
safe.uboErr(logPrefix, ex);
|
2023-05-23 16:59:27 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
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() {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( document.currentScript === thisScript ) {
|
|
|
|
|
return this.v;
|
|
|
|
|
}
|
|
|
|
|
safe.uboLog(logPrefix, 'Property read');
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
return normalValue;
|
2023-05-23 16:59:27 +02:00
|
|
|
|
},
|
|
|
|
|
setter: function(a) {
|
|
|
|
|
if ( mustAbort(a) === false ) { return; }
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
normalValue = a;
|
2023-05-23 16:59:27 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const prop = chain.slice(0, pos);
|
|
|
|
|
const v = owner[prop];
|
|
|
|
|
chain = chain.slice(pos + 1);
|
2024-01-11 17:41:37 +01:00
|
|
|
|
if ( v instanceof safe.Object || typeof v === 'object' && v !== null ) {
|
2023-05-23 16:59:27 +02:00
|
|
|
|
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;
|
2024-01-11 17:41:37 +01:00
|
|
|
|
if ( a instanceof safe.Object ) {
|
2023-05-23 16:59:27 +02:00
|
|
|
|
trapChain(a, chain);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
trapChain(window, chain);
|
|
|
|
|
}
|
|
|
|
|
runAt(( ) => {
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
setConstant(chain, rawValue);
|
Re-factor extra args for `set-constant` scriptlet
To prepare for better compatibility with AdGuard's own `set-constant`
scriptlet.
The 3rd position parameter which dictates how to set the value has
been converted into a vararg paramater, as follow:
..., as, function
..., as, callback
..., as, resolved
..., as, rejected
Similarly, the parameter used to dictate when the scriptlet
should become effective is now to be used as a vararg:
..., runAt, load
Related issue:
https://github.com/uBlockOrigin/uBlock-issues/issues/2783
Ideally, AdGuard would support its `stack` parameter as a
vararg, to be discussed.
2023-08-22 16:12:08 +02:00
|
|
|
|
}, extraArgs.runAt);
|
2023-05-23 16:59:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-25 14:51:26 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
2023-10-17 23:33:49 +02:00
|
|
|
|
name: 'replace-node-text.fn',
|
|
|
|
|
fn: replaceNodeTextFn,
|
2023-05-25 14:51:26 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'run-at.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
2023-10-17 23:33:49 +02:00
|
|
|
|
function replaceNodeTextFn(
|
2023-05-25 14:51:26 +02:00
|
|
|
|
nodeName = '',
|
|
|
|
|
pattern = '',
|
|
|
|
|
replacement = ''
|
|
|
|
|
) {
|
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('replace-node-text.fn', ...Array.from(arguments));
|
2023-10-14 03:51:13 +02:00
|
|
|
|
const reNodeName = safe.patternToRegex(nodeName, 'i', true);
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const rePattern = safe.patternToRegex(pattern, 'gms');
|
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
2023-11-10 18:29:51 +01:00
|
|
|
|
const reCondition = safe.patternToRegex(extraArgs.condition || '', 'ms');
|
2023-05-25 14:51:26 +02:00
|
|
|
|
const stop = (takeRecord = true) => {
|
|
|
|
|
if ( takeRecord ) {
|
|
|
|
|
handleMutations(observer.takeRecords());
|
|
|
|
|
}
|
|
|
|
|
observer.disconnect();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, 'Quitting');
|
2023-05-25 14:51:26 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let sedCount = extraArgs.sedCount || 0;
|
|
|
|
|
const handleNode = node => {
|
|
|
|
|
const before = node.textContent;
|
2023-11-06 02:34:11 +01:00
|
|
|
|
reCondition.lastIndex = 0;
|
2023-05-25 14:51:26 +02:00
|
|
|
|
if ( safe.RegExp_test.call(reCondition, before) === false ) { return true; }
|
2023-11-06 02:34:11 +01:00
|
|
|
|
rePattern.lastIndex = 0;
|
|
|
|
|
if ( safe.RegExp_test.call(rePattern, before) === false ) { return true; }
|
|
|
|
|
rePattern.lastIndex = 0;
|
2023-05-25 14:51:26 +02:00
|
|
|
|
const after = pattern !== ''
|
|
|
|
|
? before.replace(rePattern, replacement)
|
|
|
|
|
: replacement;
|
|
|
|
|
node.textContent = after;
|
2024-01-25 19:30:41 +01:00
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Text before:\n${before.trim()}`);
|
|
|
|
|
}
|
|
|
|
|
safe.uboLog(logPrefix, `Text after:\n${after.trim()}`);
|
2023-05-25 14:51:26 +02:00
|
|
|
|
return sedCount === 0 || (sedCount -= 1) !== 0;
|
|
|
|
|
};
|
|
|
|
|
const handleMutations = mutations => {
|
|
|
|
|
for ( const mutation of mutations ) {
|
|
|
|
|
for ( const node of mutation.addedNodes ) {
|
|
|
|
|
if ( reNodeName.test(node.nodeName) === false ) { continue; }
|
|
|
|
|
if ( handleNode(node) ) { continue; }
|
|
|
|
|
stop(false); return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const observer = new MutationObserver(handleMutations);
|
|
|
|
|
observer.observe(document, { childList: true, subtree: true });
|
|
|
|
|
if ( document.documentElement ) {
|
|
|
|
|
const treeWalker = document.createTreeWalker(
|
|
|
|
|
document.documentElement,
|
|
|
|
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
|
|
|
|
|
);
|
|
|
|
|
let count = 0;
|
|
|
|
|
for (;;) {
|
|
|
|
|
const node = treeWalker.nextNode();
|
|
|
|
|
count += 1;
|
|
|
|
|
if ( node === null ) { break; }
|
|
|
|
|
if ( reNodeName.test(node.nodeName) === false ) { continue; }
|
|
|
|
|
if ( handleNode(node) ) { continue; }
|
|
|
|
|
stop(); break;
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, `${count} nodes present before installing mutation observer`);
|
2023-05-25 14:51:26 +02:00
|
|
|
|
}
|
|
|
|
|
if ( extraArgs.stay ) { return; }
|
|
|
|
|
runAt(( ) => {
|
|
|
|
|
const quitAfter = extraArgs.quitAfter || 0;
|
|
|
|
|
if ( quitAfter !== 0 ) {
|
|
|
|
|
setTimeout(( ) => { stop(); }, quitAfter);
|
|
|
|
|
} else {
|
|
|
|
|
stop();
|
|
|
|
|
}
|
|
|
|
|
}, 'interactive');
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-29 20:37:02 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'object-prune.fn',
|
2023-10-17 23:33:49 +02:00
|
|
|
|
fn: objectPruneFn,
|
2023-05-29 20:37:02 +02:00
|
|
|
|
dependencies: [
|
2023-07-27 15:41:56 +02:00
|
|
|
|
'matches-stack-trace.fn',
|
2023-10-28 13:35:38 +02:00
|
|
|
|
'object-find-owner.fn',
|
2023-05-29 20:37:02 +02:00
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
// When no "prune paths" argument is provided, the scriptlet is
|
|
|
|
|
// used for logging purpose and the "needle paths" argument is
|
|
|
|
|
// used to filter logging output.
|
|
|
|
|
//
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/1545
|
|
|
|
|
// - Add support for "remove everything if needle matches" case
|
2023-10-17 23:33:49 +02:00
|
|
|
|
function objectPruneFn(
|
2023-05-29 20:37:02 +02:00
|
|
|
|
obj,
|
|
|
|
|
rawPrunePaths,
|
2023-07-27 15:41:56 +02:00
|
|
|
|
rawNeedlePaths,
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
stackNeedleDetails = { matchAll: true },
|
|
|
|
|
extraArgs = {}
|
2023-05-29 20:37:02 +02:00
|
|
|
|
) {
|
2023-08-09 17:05:53 +02:00
|
|
|
|
if ( typeof rawPrunePaths !== 'string' ) { return; }
|
2023-05-29 20:37:02 +02:00
|
|
|
|
const prunePaths = rawPrunePaths !== ''
|
|
|
|
|
? rawPrunePaths.split(/ +/)
|
|
|
|
|
: [];
|
2023-08-09 14:02:45 +02:00
|
|
|
|
const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== ''
|
|
|
|
|
? rawNeedlePaths.split(/ +/)
|
|
|
|
|
: [];
|
2023-07-31 15:38:04 +02:00
|
|
|
|
if ( stackNeedleDetails.matchAll !== true ) {
|
|
|
|
|
if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) {
|
2023-08-09 17:05:53 +02:00
|
|
|
|
return;
|
2023-07-27 15:41:56 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-28 13:35:38 +02:00
|
|
|
|
if ( objectPruneFn.mustProcess === undefined ) {
|
2023-10-17 23:33:49 +02:00
|
|
|
|
objectPruneFn.mustProcess = (root, needlePaths) => {
|
2023-08-09 14:02:45 +02:00
|
|
|
|
for ( const needlePath of needlePaths ) {
|
2023-10-28 13:35:38 +02:00
|
|
|
|
if ( objectFindOwnerFn(root, needlePath) === false ) {
|
2023-08-09 14:02:45 +02:00
|
|
|
|
return false;
|
2023-05-29 20:37:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-09 14:02:45 +02:00
|
|
|
|
return true;
|
|
|
|
|
};
|
2023-08-09 17:05:53 +02:00
|
|
|
|
}
|
|
|
|
|
if ( prunePaths.length === 0 ) { return; }
|
|
|
|
|
let outcome = 'nomatch';
|
2023-10-17 23:33:49 +02:00
|
|
|
|
if ( objectPruneFn.mustProcess(obj, needlePaths) ) {
|
2023-08-09 17:05:53 +02:00
|
|
|
|
for ( const path of prunePaths ) {
|
2023-10-28 13:35:38 +02:00
|
|
|
|
if ( objectFindOwnerFn(obj, path, true) ) {
|
2023-08-09 17:05:53 +02:00
|
|
|
|
outcome = 'match';
|
|
|
|
|
}
|
2023-05-29 20:37:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-09 17:05:53 +02:00
|
|
|
|
if ( outcome === 'match' ) { return obj; }
|
2023-05-29 20:37:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-16 17:32:12 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-10-28 13:35:38 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'object-find-owner.fn',
|
|
|
|
|
fn: objectFindOwnerFn,
|
|
|
|
|
});
|
|
|
|
|
function objectFindOwnerFn(
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
let modified = false;
|
|
|
|
|
if ( chain === '*' ) {
|
|
|
|
|
for ( const key in owner ) {
|
|
|
|
|
if ( owner.hasOwnProperty(key) === false ) { continue; }
|
|
|
|
|
delete owner[key];
|
|
|
|
|
modified = true;
|
|
|
|
|
}
|
|
|
|
|
} else if ( owner.hasOwnProperty(chain) ) {
|
|
|
|
|
delete owner[chain];
|
|
|
|
|
modified = true;
|
|
|
|
|
}
|
|
|
|
|
return modified;
|
|
|
|
|
}
|
|
|
|
|
const prop = chain.slice(0, pos);
|
2024-03-12 18:21:16 +01:00
|
|
|
|
const next = chain.slice(pos + 1);
|
|
|
|
|
let found = false;
|
|
|
|
|
if ( prop === '[-]' && Array.isArray(owner) ) {
|
|
|
|
|
let i = owner.length;
|
|
|
|
|
while ( i-- ) {
|
|
|
|
|
if ( objectFindOwnerFn(owner[i], next) === false ) { continue; }
|
|
|
|
|
owner.splice(i, 1);
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
|
|
|
|
return found;
|
|
|
|
|
}
|
|
|
|
|
if ( prop === '{-}' && owner instanceof Object ) {
|
|
|
|
|
for ( const key of Object.keys(owner) ) {
|
|
|
|
|
if ( objectFindOwnerFn(owner[key], next) === false ) { continue; }
|
|
|
|
|
delete owner[key];
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
|
|
|
|
return found;
|
|
|
|
|
}
|
2023-10-28 13:35:38 +02:00
|
|
|
|
if (
|
|
|
|
|
prop === '[]' && Array.isArray(owner) ||
|
2024-03-12 18:21:16 +01:00
|
|
|
|
prop === '{}' && owner instanceof Object ||
|
2023-10-28 13:35:38 +02:00
|
|
|
|
prop === '*' && owner instanceof Object
|
|
|
|
|
) {
|
|
|
|
|
for ( const key of Object.keys(owner) ) {
|
2024-03-12 18:21:16 +01:00
|
|
|
|
if (objectFindOwnerFn(owner[key], next, prune) === false ) { continue; }
|
|
|
|
|
found = true;
|
2023-10-28 13:35:38 +02:00
|
|
|
|
}
|
|
|
|
|
return found;
|
|
|
|
|
}
|
|
|
|
|
if ( owner.hasOwnProperty(prop) === false ) { return false; }
|
|
|
|
|
owner = owner[prop];
|
|
|
|
|
chain = chain.slice(pos + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2024-01-20 16:33:36 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'get-all-cookies.fn',
|
|
|
|
|
fn: getAllCookiesFn,
|
|
|
|
|
});
|
|
|
|
|
function getAllCookiesFn() {
|
|
|
|
|
return document.cookie.split(/\s*;\s*/).map(s => {
|
|
|
|
|
const pos = s.indexOf('=');
|
|
|
|
|
if ( pos === 0 ) { return; }
|
|
|
|
|
if ( pos === -1 ) { return `${s.trim()}=`; }
|
|
|
|
|
const key = s.slice(0, pos).trim();
|
|
|
|
|
const value = s.slice(pos+1).trim();
|
|
|
|
|
return { key, value };
|
|
|
|
|
}).filter(s => s !== undefined);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'get-all-local-storage.fn',
|
|
|
|
|
fn: getAllLocalStorageFn,
|
|
|
|
|
});
|
|
|
|
|
function getAllLocalStorageFn(which = 'localStorage') {
|
|
|
|
|
const storage = self[which];
|
|
|
|
|
const out = [];
|
|
|
|
|
for ( let i = 0; i < storage.length; i++ ) {
|
|
|
|
|
const key = storage.key(i);
|
|
|
|
|
const value = storage.getItem(key);
|
|
|
|
|
return { key, value };
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'get-cookie.fn',
|
|
|
|
|
fn: getCookieFn,
|
|
|
|
|
});
|
|
|
|
|
function getCookieFn(
|
|
|
|
|
name = ''
|
|
|
|
|
) {
|
|
|
|
|
for ( const s of document.cookie.split(/\s*;\s*/) ) {
|
|
|
|
|
const pos = s.indexOf('=');
|
|
|
|
|
if ( pos === -1 ) { continue; }
|
|
|
|
|
if ( s.slice(0, pos) !== name ) { continue; }
|
|
|
|
|
return s.slice(pos+1).trim();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-06-16 17:32:12 +02:00
|
|
|
|
builtinScriptlets.push({
|
2023-10-23 00:19:18 +02:00
|
|
|
|
name: 'set-cookie.fn',
|
|
|
|
|
fn: setCookieFn,
|
2024-01-20 16:33:36 +01:00
|
|
|
|
dependencies: [
|
|
|
|
|
'get-cookie.fn',
|
|
|
|
|
],
|
2023-06-16 17:32:12 +02:00
|
|
|
|
});
|
2023-10-23 00:19:18 +02:00
|
|
|
|
function setCookieFn(
|
|
|
|
|
trusted = false,
|
2023-06-16 17:32:12 +02:00
|
|
|
|
name = '',
|
|
|
|
|
value = '',
|
|
|
|
|
expires = '',
|
|
|
|
|
path = '',
|
|
|
|
|
options = {},
|
|
|
|
|
) {
|
2024-03-23 16:00:45 +01:00
|
|
|
|
// https://datatracker.ietf.org/doc/html/rfc2616#section-2.2
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/2777
|
|
|
|
|
if ( trusted === false && /[^!#$%&'*+\-.0-9A-Z[\]^_`a-z|~]/.test(name) ) {
|
|
|
|
|
name = encodeURIComponent(name);
|
|
|
|
|
}
|
|
|
|
|
// https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
|
2024-04-01 14:23:10 +02:00
|
|
|
|
// The characters [",] are given a pass from the RFC requirements because
|
|
|
|
|
// apparently browsers do not follow the RFC to the letter.
|
2024-04-10 14:24:45 +02:00
|
|
|
|
if ( /[^ -:<-[\]-~]/.test(value) ) {
|
2024-03-23 16:00:45 +01:00
|
|
|
|
value = encodeURIComponent(value);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-20 16:33:36 +01:00
|
|
|
|
const cookieBefore = getCookieFn(name);
|
2023-10-21 03:38:54 +02:00
|
|
|
|
if ( cookieBefore !== undefined && options.dontOverwrite ) { return; }
|
|
|
|
|
if ( cookieBefore === value && options.reload ) { return; }
|
2023-06-16 17:32:12 +02:00
|
|
|
|
|
|
|
|
|
const cookieParts = [ name, '=', value ];
|
|
|
|
|
if ( expires !== '' ) {
|
|
|
|
|
cookieParts.push('; expires=', expires);
|
|
|
|
|
}
|
2023-06-18 21:13:54 +02:00
|
|
|
|
|
|
|
|
|
if ( path === '' ) { path = '/'; }
|
|
|
|
|
else if ( path === 'none' ) { path = ''; }
|
|
|
|
|
if ( path !== '' && path !== '/' ) { return; }
|
|
|
|
|
if ( path === '/' ) {
|
2023-06-16 17:32:12 +02:00
|
|
|
|
cookieParts.push('; path=/');
|
|
|
|
|
}
|
2023-10-23 00:19:18 +02:00
|
|
|
|
|
|
|
|
|
if ( trusted ) {
|
|
|
|
|
if ( options.domain ) {
|
|
|
|
|
cookieParts.push(`; domain=${options.domain}`);
|
|
|
|
|
}
|
|
|
|
|
cookieParts.push('; Secure');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
document.cookie = cookieParts.join('');
|
|
|
|
|
} catch(_) {
|
|
|
|
|
}
|
2023-06-16 17:32:12 +02:00
|
|
|
|
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const done = getCookieFn(name) === value;
|
|
|
|
|
if ( done && options.reload ) {
|
2023-06-16 17:32:12 +02:00
|
|
|
|
window.location.reload();
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
|
|
|
|
|
return done;
|
2023-06-16 17:32:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-04 13:13:22 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
2023-10-21 02:10:35 +02:00
|
|
|
|
name: 'set-local-storage-item.fn',
|
|
|
|
|
fn: setLocalStorageItemFn,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
2023-07-04 13:13:22 +02:00
|
|
|
|
});
|
2023-10-21 02:10:35 +02:00
|
|
|
|
function setLocalStorageItemFn(
|
2023-07-04 13:13:22 +02:00
|
|
|
|
which = 'local',
|
|
|
|
|
trusted = false,
|
|
|
|
|
key = '',
|
|
|
|
|
value = '',
|
|
|
|
|
) {
|
|
|
|
|
if ( key === '' ) { return; }
|
|
|
|
|
|
2023-11-17 15:28:23 +01:00
|
|
|
|
// For increased compatibility with AdGuard
|
|
|
|
|
if ( value === 'emptyArr' ) {
|
|
|
|
|
value = '[]';
|
|
|
|
|
} else if ( value === 'emptyObj' ) {
|
|
|
|
|
value = '{}';
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-04 13:13:22 +02:00
|
|
|
|
const trustedValues = [
|
|
|
|
|
'',
|
|
|
|
|
'undefined', 'null',
|
|
|
|
|
'false', 'true',
|
2023-10-21 02:10:35 +02:00
|
|
|
|
'on', 'off',
|
2023-07-04 13:13:22 +02:00
|
|
|
|
'yes', 'no',
|
2024-05-10 02:51:07 +02:00
|
|
|
|
'accept', 'reject',
|
|
|
|
|
'accepted', 'rejected',
|
2023-07-04 13:13:22 +02:00
|
|
|
|
'{}', '[]', '""',
|
|
|
|
|
'$remove$',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if ( trusted ) {
|
2024-05-05 12:51:52 +02:00
|
|
|
|
if ( value.includes('$now$') ) {
|
2024-05-12 01:05:14 +02:00
|
|
|
|
value = value.replaceAll('$now$', Date.now());
|
2024-05-05 12:51:52 +02:00
|
|
|
|
}
|
|
|
|
|
if ( value.includes('$currentDate$') ) {
|
2024-05-12 01:05:14 +02:00
|
|
|
|
value = value.replaceAll('$currentDate$', `${Date()}`);
|
2024-05-05 12:51:52 +02:00
|
|
|
|
}
|
|
|
|
|
if ( value.includes('$currentISODate$') ) {
|
2024-05-12 01:05:14 +02:00
|
|
|
|
value = value.replaceAll('$currentISODate$', (new Date()).toISOString());
|
2023-07-04 13:13:22 +02:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-11-13 01:05:56 +01:00
|
|
|
|
const normalized = value.toLowerCase();
|
|
|
|
|
const match = /^("?)(.+)\1$/.exec(normalized);
|
|
|
|
|
const unquoted = match && match[2] || normalized;
|
|
|
|
|
if ( trustedValues.includes(unquoted) === false ) {
|
|
|
|
|
if ( /^\d+$/.test(unquoted) === false ) { return; }
|
2023-11-13 01:26:05 +01:00
|
|
|
|
const n = parseInt(unquoted, 10);
|
|
|
|
|
if ( n > 32767 ) { return; }
|
2023-07-04 13:13:22 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2023-10-21 02:10:35 +02:00
|
|
|
|
const storage = self[`${which}Storage`];
|
2023-07-04 13:13:22 +02:00
|
|
|
|
if ( value === '$remove$' ) {
|
2023-10-21 02:10:35 +02:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const pattern = safe.patternToRegex(key, undefined, true );
|
|
|
|
|
const toRemove = [];
|
|
|
|
|
for ( let i = 0, n = storage.length; i < n; i++ ) {
|
|
|
|
|
const key = storage.key(i);
|
|
|
|
|
if ( pattern.test(key) ) { toRemove.push(key); }
|
|
|
|
|
}
|
|
|
|
|
for ( const key of toRemove ) {
|
|
|
|
|
storage.removeItem(key);
|
|
|
|
|
}
|
2023-07-04 13:13:22 +02:00
|
|
|
|
} else {
|
2023-10-21 02:10:35 +02:00
|
|
|
|
storage.setItem(key, `${value}`);
|
2023-07-04 13:13:22 +02:00
|
|
|
|
}
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 15:41:56 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'matches-stack-trace.fn',
|
|
|
|
|
fn: matchesStackTrace,
|
|
|
|
|
dependencies: [
|
Fine tune logging capabilities of `json-prune` scriptlet
This extends logging capabilities of `json-prune` scriptlet as
follow:
...##+js(json-prune, a, b, stackNeedle, log, [logneedle], logstack, 1)
Whereas before, the only way to log `json-prune` usage was to skip
providing the property chain:
...##+js(json-prune, , b)
Where `b` was the expression to filter out logging output.
With the extended logging capabilities, the logging output can
be filtered out with `logneedle`, which can be a regex literal.
Additionally, to log the stack trace the `stackNeedle` argument
must be set to non-empty string. You can use `/.^/` to log the
stack trace without matching it.
2023-07-29 16:22:52 +02:00
|
|
|
|
'get-exception-token.fn',
|
2023-07-27 15:41:56 +02:00
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function matchesStackTrace(
|
2023-07-31 15:38:04 +02:00
|
|
|
|
needleDetails,
|
2023-12-06 16:17:19 +01:00
|
|
|
|
logLevel = ''
|
2023-07-27 15:41:56 +02:00
|
|
|
|
) {
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const exceptionToken = getExceptionToken();
|
|
|
|
|
const error = new safe.Error(exceptionToken);
|
|
|
|
|
const docURL = new URL(self.location.href);
|
|
|
|
|
docURL.hash = '';
|
|
|
|
|
// Normalize stack trace
|
|
|
|
|
const reLine = /(.*?@)?(\S+)(:\d+):\d+\)?$/;
|
|
|
|
|
const lines = [];
|
|
|
|
|
for ( let line of error.stack.split(/[\n\r]+/) ) {
|
|
|
|
|
if ( line.includes(exceptionToken) ) { continue; }
|
|
|
|
|
line = line.trim();
|
|
|
|
|
const match = safe.RegExp_exec.call(reLine, line);
|
|
|
|
|
if ( match === null ) { continue; }
|
|
|
|
|
let url = match[2];
|
|
|
|
|
if ( url.startsWith('(') ) { url = url.slice(1); }
|
|
|
|
|
if ( url === docURL.href ) {
|
|
|
|
|
url = 'inlineScript';
|
|
|
|
|
} else if ( url.startsWith('<anonymous>') ) {
|
|
|
|
|
url = 'injectedScript';
|
|
|
|
|
}
|
|
|
|
|
let fn = match[1] !== undefined
|
|
|
|
|
? match[1].slice(0, -1)
|
|
|
|
|
: line.slice(0, match.index).trim();
|
|
|
|
|
if ( fn.startsWith('at') ) { fn = fn.slice(2).trim(); }
|
|
|
|
|
let rowcol = match[3];
|
|
|
|
|
lines.push(' ' + `${fn} ${url}${rowcol}:1`.trim());
|
|
|
|
|
}
|
|
|
|
|
lines[0] = `stackDepth:${lines.length-1}`;
|
|
|
|
|
const stack = lines.join('\t');
|
2023-12-06 16:17:19 +01:00
|
|
|
|
const r = needleDetails.matchAll !== true &&
|
|
|
|
|
safe.testPattern(needleDetails, stack);
|
2023-07-27 15:41:56 +02:00
|
|
|
|
if (
|
2023-12-06 16:17:19 +01:00
|
|
|
|
logLevel === 'all' ||
|
|
|
|
|
logLevel === 'match' && r ||
|
|
|
|
|
logLevel === 'nomatch' && !r
|
2023-07-27 15:41:56 +02:00
|
|
|
|
) {
|
|
|
|
|
safe.uboLog(stack.replace(/\t/g, '\n'));
|
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'parse-properties-to-match.fn',
|
|
|
|
|
fn: parsePropertiesToMatch,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function parsePropertiesToMatch(propsToMatch, implicit = '') {
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const needles = new Map();
|
|
|
|
|
if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; }
|
2023-08-08 18:20:03 +02:00
|
|
|
|
const options = { canNegate: true };
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
for ( const needle of propsToMatch.split(/\s+/) ) {
|
|
|
|
|
const [ prop, pattern ] = needle.split(':');
|
|
|
|
|
if ( prop === '' ) { continue; }
|
|
|
|
|
if ( pattern !== undefined ) {
|
2023-08-08 18:20:03 +02:00
|
|
|
|
needles.set(prop, safe.initPattern(pattern, options));
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
} else if ( implicit !== '' ) {
|
2023-08-08 18:20:03 +02:00
|
|
|
|
needles.set(implicit, safe.initPattern(prop, options));
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return needles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'match-object-properties.fn',
|
|
|
|
|
fn: matchObjectProperties,
|
2023-08-08 18:20:03 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
});
|
|
|
|
|
function matchObjectProperties(propNeedles, ...objs) {
|
|
|
|
|
if ( matchObjectProperties.extractProperties === undefined ) {
|
|
|
|
|
matchObjectProperties.extractProperties = (src, des, props) => {
|
|
|
|
|
for ( const p of props ) {
|
|
|
|
|
const v = src[p];
|
|
|
|
|
if ( v === undefined ) { continue; }
|
|
|
|
|
des[p] = src[p];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2023-08-08 18:20:03 +02:00
|
|
|
|
const safe = safeSelf();
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
const haystack = {};
|
2023-10-14 14:03:29 +02:00
|
|
|
|
const props = safe.Array_from(propNeedles.keys());
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
for ( const obj of objs ) {
|
|
|
|
|
if ( obj instanceof Object === false ) { continue; }
|
|
|
|
|
matchObjectProperties.extractProperties(obj, haystack, props);
|
|
|
|
|
}
|
|
|
|
|
for ( const [ prop, details ] of propNeedles ) {
|
|
|
|
|
let value = haystack[prop];
|
|
|
|
|
if ( value === undefined ) { continue; }
|
|
|
|
|
if ( typeof value !== 'string' ) {
|
2024-01-28 16:58:41 +01:00
|
|
|
|
try { value = safe.JSON_stringify(value); }
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
catch(ex) { }
|
2023-08-08 18:20:03 +02:00
|
|
|
|
if ( typeof value !== 'string' ) { continue; }
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
}
|
2023-08-08 18:20:03 +02:00
|
|
|
|
if ( safe.testPattern(details, value) ) { continue; }
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 17:44:18 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'json-prune-fetch-response.fn',
|
|
|
|
|
fn: jsonPruneFetchResponseFn,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'match-object-properties.fn',
|
|
|
|
|
'object-prune.fn',
|
|
|
|
|
'parse-properties-to-match.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function jsonPruneFetchResponseFn(
|
|
|
|
|
rawPrunePaths = '',
|
|
|
|
|
rawNeedlePaths = ''
|
|
|
|
|
) {
|
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('json-prune-fetch-response', rawPrunePaths, rawNeedlePaths);
|
2023-10-07 17:44:18 +02:00
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
|
|
|
|
|
const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url');
|
|
|
|
|
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
|
2024-02-19 16:59:12 +01:00
|
|
|
|
const logall = rawPrunePaths === '';
|
2023-10-07 17:44:18 +02:00
|
|
|
|
const applyHandler = function(target, thisArg, args) {
|
|
|
|
|
const fetchPromise = Reflect.apply(target, thisArg, args);
|
2024-02-19 16:59:12 +01:00
|
|
|
|
let outcome = logall ? 'nomatch' : 'match';
|
2023-10-07 17:44:18 +02:00
|
|
|
|
if ( propNeedles.size !== 0 ) {
|
|
|
|
|
const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ];
|
2023-10-17 01:36:16 +02:00
|
|
|
|
if ( objs[0] instanceof Request ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
try {
|
|
|
|
|
objs[0] = safe.Request_clone.call(objs[0]);
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
safe.uboErr(logPrefix, 'Error:', ex);
|
|
|
|
|
}
|
2023-10-07 17:44:18 +02:00
|
|
|
|
}
|
|
|
|
|
if ( args[1] instanceof Object ) {
|
|
|
|
|
objs.push(args[1]);
|
|
|
|
|
}
|
|
|
|
|
if ( matchObjectProperties(propNeedles, ...objs) === false ) {
|
|
|
|
|
outcome = 'nomatch';
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-19 16:59:12 +01:00
|
|
|
|
if ( logall === false && outcome === 'nomatch' ) { return fetchPromise; }
|
|
|
|
|
if ( safe.logLevel > 1 && outcome !== 'nomatch' && propNeedles.size !== 0 ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Matched optional "propsToMatch"\n${extraArgs.propsToMatch}`);
|
|
|
|
|
}
|
2023-10-07 17:44:18 +02:00
|
|
|
|
return fetchPromise.then(responseBefore => {
|
|
|
|
|
const response = responseBefore.clone();
|
|
|
|
|
return response.json().then(objBefore => {
|
|
|
|
|
if ( typeof objBefore !== 'object' ) { return responseBefore; }
|
2024-02-19 16:59:12 +01:00
|
|
|
|
if ( logall ) {
|
|
|
|
|
safe.uboLog(logPrefix, safe.JSON_stringify(objBefore, null, 2));
|
|
|
|
|
return responseBefore;
|
|
|
|
|
}
|
2023-10-17 23:33:49 +02:00
|
|
|
|
const objAfter = objectPruneFn(
|
2023-10-07 17:44:18 +02:00
|
|
|
|
objBefore,
|
|
|
|
|
rawPrunePaths,
|
|
|
|
|
rawNeedlePaths,
|
|
|
|
|
stackNeedle,
|
|
|
|
|
extraArgs
|
|
|
|
|
);
|
|
|
|
|
if ( typeof objAfter !== 'object' ) { return responseBefore; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'Pruned');
|
2023-10-07 17:44:18 +02:00
|
|
|
|
const responseAfter = Response.json(objAfter, {
|
|
|
|
|
status: responseBefore.status,
|
|
|
|
|
statusText: responseBefore.statusText,
|
|
|
|
|
headers: responseBefore.headers,
|
|
|
|
|
});
|
|
|
|
|
Object.defineProperties(responseAfter, {
|
|
|
|
|
ok: { value: responseBefore.ok },
|
|
|
|
|
redirected: { value: responseBefore.redirected },
|
|
|
|
|
type: { value: responseBefore.type },
|
|
|
|
|
url: { value: responseBefore.url },
|
|
|
|
|
});
|
|
|
|
|
return responseAfter;
|
|
|
|
|
}).catch(reason => {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboErr(logPrefix, 'Error:', reason);
|
2023-10-07 17:44:18 +02:00
|
|
|
|
return responseBefore;
|
|
|
|
|
});
|
|
|
|
|
}).catch(reason => {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboErr(logPrefix, 'Error:', reason);
|
2023-10-07 17:44:18 +02:00
|
|
|
|
return fetchPromise;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
self.fetch = new Proxy(self.fetch, {
|
|
|
|
|
apply: applyHandler
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'replace-fetch-response.fn',
|
|
|
|
|
fn: replaceFetchResponseFn,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'match-object-properties.fn',
|
|
|
|
|
'parse-properties-to-match.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function replaceFetchResponseFn(
|
|
|
|
|
trusted = false,
|
|
|
|
|
pattern = '',
|
|
|
|
|
replacement = '',
|
|
|
|
|
propsToMatch = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( trusted !== true ) { return; }
|
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('replace-fetch-response', pattern, replacement, propsToMatch);
|
2023-10-07 17:44:18 +02:00
|
|
|
|
if ( pattern === '*' ) { pattern = '.*'; }
|
|
|
|
|
const rePattern = safe.patternToRegex(pattern);
|
|
|
|
|
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
|
|
|
|
|
self.fetch = new Proxy(self.fetch, {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
|
const fetchPromise = Reflect.apply(target, thisArg, args);
|
|
|
|
|
if ( pattern === '' ) { return fetchPromise; }
|
|
|
|
|
let outcome = 'match';
|
|
|
|
|
if ( propNeedles.size !== 0 ) {
|
|
|
|
|
const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ];
|
2023-10-17 01:36:16 +02:00
|
|
|
|
if ( objs[0] instanceof Request ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
try {
|
|
|
|
|
objs[0] = safe.Request_clone.call(objs[0]);
|
|
|
|
|
}
|
|
|
|
|
catch(ex) {
|
|
|
|
|
safe.uboErr(logPrefix, ex);
|
|
|
|
|
}
|
2023-10-07 17:44:18 +02:00
|
|
|
|
}
|
|
|
|
|
if ( args[1] instanceof Object ) {
|
|
|
|
|
objs.push(args[1]);
|
|
|
|
|
}
|
|
|
|
|
if ( matchObjectProperties(propNeedles, ...objs) === false ) {
|
|
|
|
|
outcome = 'nomatch';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( outcome === 'nomatch' ) { return fetchPromise; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Matched "propsToMatch"\n${propsToMatch}`);
|
|
|
|
|
}
|
2023-10-07 17:44:18 +02:00
|
|
|
|
return fetchPromise.then(responseBefore => {
|
|
|
|
|
const response = responseBefore.clone();
|
|
|
|
|
return response.text().then(textBefore => {
|
|
|
|
|
const textAfter = textBefore.replace(rePattern, replacement);
|
|
|
|
|
const outcome = textAfter !== textBefore ? 'match' : 'nomatch';
|
|
|
|
|
if ( outcome === 'nomatch' ) { return responseBefore; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'Replaced');
|
2023-10-07 17:44:18 +02:00
|
|
|
|
const responseAfter = new Response(textAfter, {
|
|
|
|
|
status: responseBefore.status,
|
|
|
|
|
statusText: responseBefore.statusText,
|
|
|
|
|
headers: responseBefore.headers,
|
|
|
|
|
});
|
|
|
|
|
Object.defineProperties(responseAfter, {
|
|
|
|
|
ok: { value: responseBefore.ok },
|
|
|
|
|
redirected: { value: responseBefore.redirected },
|
|
|
|
|
type: { value: responseBefore.type },
|
|
|
|
|
url: { value: responseBefore.url },
|
|
|
|
|
});
|
|
|
|
|
return responseAfter;
|
|
|
|
|
}).catch(reason => {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboErr(logPrefix, reason);
|
2023-10-07 17:44:18 +02:00
|
|
|
|
return responseBefore;
|
|
|
|
|
});
|
|
|
|
|
}).catch(reason => {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboErr(logPrefix, reason);
|
2023-10-07 17:44:18 +02:00
|
|
|
|
return fetchPromise;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'proxy-apply.fn',
|
|
|
|
|
fn: proxyApplyFn,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function proxyApplyFn(
|
|
|
|
|
target = '',
|
|
|
|
|
handler = ''
|
|
|
|
|
) {
|
|
|
|
|
let context = globalThis;
|
|
|
|
|
let prop = target;
|
|
|
|
|
for (;;) {
|
|
|
|
|
const pos = prop.indexOf('.');
|
|
|
|
|
if ( pos === -1 ) { break; }
|
|
|
|
|
context = context[prop.slice(0, pos)];
|
|
|
|
|
if ( context instanceof Object === false ) { return; }
|
|
|
|
|
prop = prop.slice(pos+1);
|
|
|
|
|
}
|
|
|
|
|
const fn = context[prop];
|
|
|
|
|
if ( typeof fn !== 'function' ) { return; }
|
|
|
|
|
if ( fn.prototype && fn.prototype.constructor === fn ) {
|
|
|
|
|
context[prop] = new Proxy(fn, { construct: handler });
|
|
|
|
|
return (...args) => { return Reflect.construct(...args); };
|
|
|
|
|
}
|
|
|
|
|
context[prop] = new Proxy(fn, { apply: handler });
|
|
|
|
|
return (...args) => { return Reflect.apply(...args); };
|
|
|
|
|
}
|
2023-10-07 17:44:18 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
|
|
Injectable scriptlets
|
|
|
|
|
|
|
|
|
|
These are meant to be used in the MAIN (webpage) execution world.
|
|
|
|
|
|
|
|
|
|
*******************************************************************************/
|
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'abort-current-script.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'acs.js',
|
|
|
|
|
'abort-current-inline-script.js',
|
|
|
|
|
'acis.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: abortCurrentScript,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-05-24 16:32:03 +02:00
|
|
|
|
'abort-current-script-core.fn',
|
|
|
|
|
'run-at-html-element.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
2022-06-24 19:35:20 +02:00
|
|
|
|
// Issues to mind before changing anything:
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/2154
|
2023-07-25 15:22:47 +02:00
|
|
|
|
function abortCurrentScript(...args) {
|
2023-10-15 17:08:15 +02:00
|
|
|
|
runAtHtmlElementFn(( ) => {
|
2023-07-25 15:22:47 +02:00
|
|
|
|
abortCurrentScriptCore(...args);
|
2023-05-24 16:32:03 +02:00
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'abort-on-property-read.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'aopr.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: abortOnPropertyRead,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'get-exception-token.fn',
|
2024-01-25 18:20:38 +01:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function abortOnPropertyRead(
|
|
|
|
|
chain = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof chain !== 'string' ) { return; }
|
|
|
|
|
if ( chain === '' ) { return; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('abort-on-property-read', chain);
|
2023-03-26 15:13:17 +02:00
|
|
|
|
const exceptionToken = getExceptionToken();
|
2019-07-06 18:36:28 +02:00
|
|
|
|
const abort = function() {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'Aborted');
|
2023-03-26 15:13:17 +02:00
|
|
|
|
throw new ReferenceError(exceptionToken);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
};
|
|
|
|
|
const makeProxy = function(owner, chain) {
|
|
|
|
|
const pos = chain.indexOf('.');
|
|
|
|
|
if ( pos === -1 ) {
|
|
|
|
|
const desc = Object.getOwnPropertyDescriptor(owner, chain);
|
|
|
|
|
if ( !desc || desc.get !== abort ) {
|
|
|
|
|
Object.defineProperty(owner, chain, {
|
|
|
|
|
get: abort,
|
|
|
|
|
set: function(){}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const prop = chain.slice(0, pos);
|
|
|
|
|
let v = owner[prop];
|
2019-07-22 13:32:39 +02:00
|
|
|
|
chain = chain.slice(pos + 1);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
if ( v ) {
|
|
|
|
|
makeProxy(v, chain);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const desc = Object.getOwnPropertyDescriptor(owner, prop);
|
|
|
|
|
if ( desc && desc.set !== undefined ) { return; }
|
|
|
|
|
Object.defineProperty(owner, prop, {
|
|
|
|
|
get: function() { return v; },
|
|
|
|
|
set: function(a) {
|
|
|
|
|
v = a;
|
|
|
|
|
if ( a instanceof Object ) {
|
|
|
|
|
makeProxy(a, chain);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
const owner = window;
|
|
|
|
|
makeProxy(owner, chain);
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'abort-on-property-write.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'aopw.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: abortOnPropertyWrite,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'get-exception-token.fn',
|
2024-01-25 18:20:38 +01:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function abortOnPropertyWrite(
|
|
|
|
|
prop = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof prop !== 'string' ) { return; }
|
|
|
|
|
if ( prop === '' ) { return; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const safe = safeSelf();
|
2024-02-15 15:47:15 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('abort-on-property-write', prop);
|
2023-03-26 15:13:17 +02:00
|
|
|
|
const exceptionToken = getExceptionToken();
|
2019-07-06 18:36:28 +02:00
|
|
|
|
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() {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'Aborted');
|
2023-03-26 15:13:17 +02:00
|
|
|
|
throw new ReferenceError(exceptionToken);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'abort-on-stack-trace.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'aost.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: abortOnStackTrace,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'get-exception-token.fn',
|
2023-07-27 15:41:56 +02:00
|
|
|
|
'matches-stack-trace.fn',
|
2023-07-31 15:38:04 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function abortOnStackTrace(
|
|
|
|
|
chain = '',
|
Fine tune logging capabilities of `json-prune` scriptlet
This extends logging capabilities of `json-prune` scriptlet as
follow:
...##+js(json-prune, a, b, stackNeedle, log, [logneedle], logstack, 1)
Whereas before, the only way to log `json-prune` usage was to skip
providing the property chain:
...##+js(json-prune, , b)
Where `b` was the expression to filter out logging output.
With the extended logging capabilities, the logging output can
be filtered out with `logneedle`, which can be a regex literal.
Additionally, to log the stack trace the `stackNeedle` argument
must be set to non-empty string. You can use `/.^/` to log the
stack trace without matching it.
2023-07-29 16:22:52 +02:00
|
|
|
|
needle = ''
|
2023-03-24 19:05:18 +01:00
|
|
|
|
) {
|
|
|
|
|
if ( typeof chain !== 'string' ) { return; }
|
2023-07-31 15:38:04 +02:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const needleDetails = safe.initPattern(needle, { canNegate: true });
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
|
2023-12-06 16:17:19 +01:00
|
|
|
|
if ( needle === '' ) { extraArgs.log = 'all'; }
|
2020-09-22 15:59:04 +02:00
|
|
|
|
const makeProxy = function(owner, chain) {
|
|
|
|
|
const pos = chain.indexOf('.');
|
|
|
|
|
if ( pos === -1 ) {
|
|
|
|
|
let v = owner[chain];
|
|
|
|
|
Object.defineProperty(owner, chain, {
|
|
|
|
|
get: function() {
|
2023-07-31 15:38:04 +02:00
|
|
|
|
if ( matchesStackTrace(needleDetails, extraArgs.log) ) {
|
2023-07-27 15:41:56 +02:00
|
|
|
|
throw new ReferenceError(getExceptionToken());
|
2020-09-22 15:59:04 +02:00
|
|
|
|
}
|
|
|
|
|
return v;
|
|
|
|
|
},
|
|
|
|
|
set: function(a) {
|
2023-07-31 15:38:04 +02:00
|
|
|
|
if ( matchesStackTrace(needleDetails, extraArgs.log) ) {
|
2023-07-27 15:41:56 +02:00
|
|
|
|
throw new ReferenceError(getExceptionToken());
|
2020-09-22 15:59:04 +02:00
|
|
|
|
}
|
|
|
|
|
v = a;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const prop = chain.slice(0, pos);
|
|
|
|
|
let v = owner[prop];
|
|
|
|
|
chain = chain.slice(pos + 1);
|
|
|
|
|
if ( v ) {
|
|
|
|
|
makeProxy(v, chain);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const desc = Object.getOwnPropertyDescriptor(owner, prop);
|
|
|
|
|
if ( desc && desc.set !== undefined ) { return; }
|
|
|
|
|
Object.defineProperty(owner, prop, {
|
|
|
|
|
get: function() { return v; },
|
|
|
|
|
set: function(a) {
|
|
|
|
|
v = a;
|
|
|
|
|
if ( a instanceof Object ) {
|
|
|
|
|
makeProxy(a, chain);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
const owner = window;
|
|
|
|
|
makeProxy(owner, chain);
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2020-09-22 15:59:04 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2020-09-22 15:59:04 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'addEventListener-defuser.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'aeld.js',
|
|
|
|
|
'prevent-addEventListener.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: addEventListenerDefuser,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-04-27 18:52:17 +02:00
|
|
|
|
'run-at.fn',
|
2023-03-26 20:02:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-04-02 18:01:58 +02:00
|
|
|
|
'should-debug.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
2021-05-26 13:22:17 +02:00
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120
|
2023-03-24 19:05:18 +01:00
|
|
|
|
function addEventListenerDefuser(
|
Simplify passing extra parameters in scriptlets
When scriptlets can receive extra optional paramaters, these will
now be passed as pair of extra paramaters in the filter declaration,
whereas each pair is a `name, value` instance.
As a result, the optional paramaters that can be passed to the
`aeld` scriptlet can be passed this way, i.e. no longer need
a JSON approach, example:
github.com##+js(aeld, click, , log, 1)
github.com##+js(aeld, , , runAt, idle, log, 1)
The non-optional paramaters are always positional, after which
the optional paramaters are non-positional pairs of values.
2023-05-24 17:59:17 +02:00
|
|
|
|
type = '',
|
|
|
|
|
pattern = ''
|
2023-03-24 19:05:18 +01:00
|
|
|
|
) {
|
2023-03-26 20:02:21 +02:00
|
|
|
|
const safe = safeSelf();
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
|
2024-02-14 14:23:16 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('prevent-addEventListener', type, pattern);
|
2023-10-24 03:15:00 +02:00
|
|
|
|
const reType = safe.patternToRegex(type, undefined, true);
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const rePattern = safe.patternToRegex(pattern);
|
Simplify passing extra parameters in scriptlets
When scriptlets can receive extra optional paramaters, these will
now be passed as pair of extra paramaters in the filter declaration,
whereas each pair is a `name, value` instance.
As a result, the optional paramaters that can be passed to the
`aeld` scriptlet can be passed this way, i.e. no longer need
a JSON approach, example:
github.com##+js(aeld, click, , log, 1)
github.com##+js(aeld, , , runAt, idle, log, 1)
The non-optional paramaters are always positional, after which
the optional paramaters are non-positional pairs of values.
2023-05-24 17:59:17 +02:00
|
|
|
|
const debug = shouldDebug(extraArgs);
|
2024-01-11 17:41:37 +01:00
|
|
|
|
const targetSelector = extraArgs.elements || undefined;
|
2024-02-14 14:23:16 +01:00
|
|
|
|
const elementMatches = elem => {
|
|
|
|
|
if ( elem && elem.matches && elem.matches(targetSelector) ) { return true; }
|
|
|
|
|
const elems = Array.from(document.querySelectorAll(targetSelector));
|
|
|
|
|
return elems.includes(elem);
|
|
|
|
|
};
|
|
|
|
|
const elementDetails = elem => {
|
|
|
|
|
if ( elem instanceof Window ) { return 'window'; }
|
|
|
|
|
if ( elem instanceof Document ) { return 'document'; }
|
|
|
|
|
if ( elem instanceof Element === false ) { return '?'; }
|
|
|
|
|
const parts = [];
|
|
|
|
|
if ( elem.id !== '' ) { parts.push(`#${CSS.escape(elem.id)}`); }
|
|
|
|
|
for ( let i = 0; i < elem.classList.length; i++ ) {
|
|
|
|
|
parts.push(`.${CSS.escape(elem.classList.item(i))}`);
|
|
|
|
|
}
|
|
|
|
|
for ( let i = 0; i < elem.attributes.length; i++ ) {
|
|
|
|
|
const attr = elem.attributes.item(i);
|
|
|
|
|
if ( attr.name === 'id' ) { continue; }
|
|
|
|
|
if ( attr.name === 'class' ) { continue; }
|
|
|
|
|
parts.push(`[${CSS.escape(attr.name)}="${attr.value}"]`);
|
2024-01-11 17:41:37 +01:00
|
|
|
|
}
|
2024-02-14 14:23:16 +01:00
|
|
|
|
return parts.join('');
|
|
|
|
|
};
|
|
|
|
|
const shouldPrevent = (thisArg, type, handler) => {
|
2024-01-10 18:46:23 +01:00
|
|
|
|
const matchesType = safe.RegExp_test.call(reType, type);
|
|
|
|
|
const matchesHandler = safe.RegExp_test.call(rePattern, handler);
|
|
|
|
|
const matchesEither = matchesType || matchesHandler;
|
|
|
|
|
const matchesBoth = matchesType && matchesHandler;
|
|
|
|
|
if ( debug === 1 && matchesBoth || debug === 2 && matchesEither ) {
|
2024-03-20 14:31:17 +01:00
|
|
|
|
debugger; // eslint-disable-line no-debugger
|
2024-01-10 18:46:23 +01:00
|
|
|
|
}
|
2024-02-14 14:23:16 +01:00
|
|
|
|
if ( matchesBoth && targetSelector !== undefined ) {
|
|
|
|
|
if ( elementMatches(thisArg) === false ) { return false; }
|
|
|
|
|
}
|
2024-01-10 18:46:23 +01:00
|
|
|
|
return matchesBoth;
|
|
|
|
|
};
|
2023-04-27 18:52:17 +02:00
|
|
|
|
const trapEddEventListeners = ( ) => {
|
|
|
|
|
const eventListenerHandler = {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
2024-01-28 16:27:46 +01:00
|
|
|
|
let t, h;
|
2023-04-27 18:52:17 +02:00
|
|
|
|
try {
|
2024-01-28 16:27:46 +01:00
|
|
|
|
t = String(args[0]);
|
2024-02-14 14:23:16 +01:00
|
|
|
|
if ( typeof args[1] === 'function' ) {
|
|
|
|
|
h = String(safe.Function_toString(args[1]));
|
|
|
|
|
} else if ( typeof args[1] === 'object' && args[1] !== null ) {
|
|
|
|
|
if ( typeof args[1].handleEvent === 'function' ) {
|
|
|
|
|
h = String(safe.Function_toString(args[1].handleEvent));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
h = String(args[1]);
|
|
|
|
|
}
|
2023-04-27 18:52:17 +02:00
|
|
|
|
} catch(ex) {
|
|
|
|
|
}
|
2024-01-28 16:27:46 +01:00
|
|
|
|
if ( type === '' && pattern === '' ) {
|
2024-02-14 14:23:16 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`);
|
2024-01-28 16:27:46 +01:00
|
|
|
|
} else if ( shouldPrevent(thisArg, t, h) ) {
|
2024-02-14 14:23:16 +01:00
|
|
|
|
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
|
2024-01-25 18:20:38 +01:00
|
|
|
|
}
|
2024-01-28 16:27:46 +01:00
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
2023-05-28 20:56:31 +02:00
|
|
|
|
},
|
|
|
|
|
get(target, prop, receiver) {
|
|
|
|
|
if ( prop === 'toString' ) {
|
|
|
|
|
return target.toString.bind(target);
|
|
|
|
|
}
|
|
|
|
|
return Reflect.get(target, prop, receiver);
|
|
|
|
|
},
|
2023-04-27 18:52:17 +02:00
|
|
|
|
};
|
|
|
|
|
self.EventTarget.prototype.addEventListener = new Proxy(
|
|
|
|
|
self.EventTarget.prototype.addEventListener,
|
|
|
|
|
eventListenerHandler
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
runAt(( ) => {
|
|
|
|
|
trapEddEventListeners();
|
Simplify passing extra parameters in scriptlets
When scriptlets can receive extra optional paramaters, these will
now be passed as pair of extra paramaters in the filter declaration,
whereas each pair is a `name, value` instance.
As a result, the optional paramaters that can be passed to the
`aeld` scriptlet can be passed this way, i.e. no longer need
a JSON approach, example:
github.com##+js(aeld, click, , log, 1)
github.com##+js(aeld, , , runAt, idle, log, 1)
The non-optional paramaters are always positional, after which
the optional paramaters are non-positional pairs of values.
2023-05-24 17:59:17 +02:00
|
|
|
|
}, extraArgs.runAt);
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'json-prune.js',
|
|
|
|
|
fn: jsonPrune,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-05-29 20:37:02 +02:00
|
|
|
|
'object-prune.fn',
|
2023-07-31 15:38:04 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function jsonPrune(
|
|
|
|
|
rawPrunePaths = '',
|
2023-07-27 15:41:56 +02:00
|
|
|
|
rawNeedlePaths = '',
|
|
|
|
|
stackNeedle = ''
|
2023-03-24 19:05:18 +01:00
|
|
|
|
) {
|
2023-07-31 15:38:04 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-01-27 12:43:36 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('json-prune', rawPrunePaths, rawNeedlePaths, stackNeedle);
|
2023-07-31 15:38:04 +02:00
|
|
|
|
const stackNeedleDetails = safe.initPattern(stackNeedle, { canNegate: true });
|
Add variable argument `fetchPropsToMatch` to `json-prune` scriptlet
`fetchPropsToMatch` is an optional variable argument. If provided,
the scriplet will take effect only when the JSON data is obtained
through `Response.json()` and if there is a match with the value of
`fetchPropsToMatch` and the properties of the `Response` instance.
Examples of usage:
...##+js(json-prune, ads, , , fetchPropsToMatch, ?param=)
...##+js(json-prune, ads, , , fetchPropsToMatch, url:?param= method:get)
The optional variable argument `fetchPropsToMatch` acts as an additional
narrowing condition to fulfill before the JSON data is pruned.
2023-08-08 16:18:34 +02:00
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
2023-08-23 14:49:22 +02:00
|
|
|
|
JSON.parse = new Proxy(JSON.parse, {
|
2023-05-29 20:37:02 +02:00
|
|
|
|
apply: function(target, thisArg, args) {
|
2023-08-23 14:49:22 +02:00
|
|
|
|
const objBefore = Reflect.apply(target, thisArg, args);
|
2024-01-27 12:43:36 +01:00
|
|
|
|
if ( rawPrunePaths === '' ) {
|
2024-01-28 16:58:41 +01:00
|
|
|
|
safe.uboLog(logPrefix, safe.JSON_stringify(objBefore, null, 2));
|
2024-01-27 12:43:36 +01:00
|
|
|
|
}
|
2023-10-17 23:33:49 +02:00
|
|
|
|
const objAfter = objectPruneFn(
|
2023-08-23 14:49:22 +02:00
|
|
|
|
objBefore,
|
|
|
|
|
rawPrunePaths,
|
|
|
|
|
rawNeedlePaths,
|
|
|
|
|
stackNeedleDetails,
|
|
|
|
|
extraArgs
|
2024-01-27 12:43:36 +01:00
|
|
|
|
);
|
|
|
|
|
if ( objAfter === undefined ) { return objBefore; }
|
|
|
|
|
safe.uboLog(logPrefix, 'Pruned');
|
|
|
|
|
if ( safe.logLevel > 1 ) {
|
2024-01-28 16:58:41 +01:00
|
|
|
|
safe.uboLog(logPrefix, `After pruning:\n${safe.JSON_stringify(objAfter, null, 2)}`);
|
2024-01-27 12:43:36 +01:00
|
|
|
|
}
|
|
|
|
|
return objAfter;
|
2023-08-23 14:49:22 +02:00
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* json-prune-fetch-response.js
|
|
|
|
|
*
|
|
|
|
|
* Prune JSON response of fetch requests.
|
|
|
|
|
*
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'json-prune-fetch-response.js',
|
|
|
|
|
fn: jsonPruneFetchResponse,
|
|
|
|
|
dependencies: [
|
2023-10-07 17:44:18 +02:00
|
|
|
|
'json-prune-fetch-response.fn',
|
2023-08-23 14:49:22 +02:00
|
|
|
|
],
|
|
|
|
|
});
|
2023-10-07 17:44:18 +02:00
|
|
|
|
function jsonPruneFetchResponse(...args) {
|
|
|
|
|
jsonPruneFetchResponseFn(...args);
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-09-09 20:06:23 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-09-09 20:06:23 +02:00
|
|
|
|
|
2023-09-04 20:54:57 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'json-prune-xhr-response.js',
|
|
|
|
|
fn: jsonPruneXhrResponse,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'match-object-properties.fn',
|
|
|
|
|
'object-prune.fn',
|
|
|
|
|
'parse-properties-to-match.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function jsonPruneXhrResponse(
|
|
|
|
|
rawPrunePaths = '',
|
|
|
|
|
rawNeedlePaths = ''
|
|
|
|
|
) {
|
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('json-prune-xhr-response', rawPrunePaths, rawNeedlePaths);
|
2023-09-04 20:54:57 +02:00
|
|
|
|
const xhrInstances = new WeakMap();
|
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
|
|
|
|
|
const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url');
|
2023-10-05 17:24:35 +02:00
|
|
|
|
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
|
2023-09-04 20:54:57 +02:00
|
|
|
|
self.XMLHttpRequest = class extends self.XMLHttpRequest {
|
|
|
|
|
open(method, url, ...args) {
|
2023-09-05 20:11:33 +02:00
|
|
|
|
const xhrDetails = { method, url };
|
2023-09-04 20:54:57 +02:00
|
|
|
|
let outcome = 'match';
|
|
|
|
|
if ( propNeedles.size !== 0 ) {
|
2023-09-05 20:11:33 +02:00
|
|
|
|
if ( matchObjectProperties(propNeedles, xhrDetails) === false ) {
|
2023-09-04 20:54:57 +02:00
|
|
|
|
outcome = 'nomatch';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( outcome === 'match' ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Matched optional "propsToMatch", "${extraArgs.propsToMatch}"`);
|
|
|
|
|
}
|
2023-09-05 20:11:33 +02:00
|
|
|
|
xhrInstances.set(this, xhrDetails);
|
2023-09-04 20:54:57 +02:00
|
|
|
|
}
|
|
|
|
|
return super.open(method, url, ...args);
|
|
|
|
|
}
|
2023-09-05 20:11:33 +02:00
|
|
|
|
get response() {
|
|
|
|
|
const innerResponse = super.response;
|
|
|
|
|
const xhrDetails = xhrInstances.get(this);
|
2023-09-04 20:54:57 +02:00
|
|
|
|
if ( xhrDetails === undefined ) {
|
2023-09-05 20:11:33 +02:00
|
|
|
|
return innerResponse;
|
2023-09-04 20:54:57 +02:00
|
|
|
|
}
|
2023-09-22 15:33:02 +02:00
|
|
|
|
const responseLength = typeof innerResponse === 'string'
|
|
|
|
|
? innerResponse.length
|
|
|
|
|
: undefined;
|
|
|
|
|
if ( xhrDetails.lastResponseLength !== responseLength ) {
|
2023-09-19 08:39:17 +02:00
|
|
|
|
xhrDetails.response = undefined;
|
2023-09-22 15:33:02 +02:00
|
|
|
|
xhrDetails.lastResponseLength = responseLength;
|
2023-09-19 08:39:17 +02:00
|
|
|
|
}
|
2023-09-06 19:04:43 +02:00
|
|
|
|
if ( xhrDetails.response !== undefined ) {
|
|
|
|
|
return xhrDetails.response;
|
2023-09-05 20:11:33 +02:00
|
|
|
|
}
|
2023-09-06 19:04:43 +02:00
|
|
|
|
let objBefore;
|
|
|
|
|
if ( typeof innerResponse === 'object' ) {
|
|
|
|
|
objBefore = innerResponse;
|
|
|
|
|
} else if ( typeof innerResponse === 'string' ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
try {
|
|
|
|
|
objBefore = safe.JSON_parse(innerResponse);
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
}
|
2023-09-05 20:11:33 +02:00
|
|
|
|
}
|
2023-09-06 19:04:43 +02:00
|
|
|
|
if ( typeof objBefore !== 'object' ) {
|
|
|
|
|
return (xhrDetails.response = innerResponse);
|
|
|
|
|
}
|
2023-10-17 23:33:49 +02:00
|
|
|
|
const objAfter = objectPruneFn(
|
2023-09-06 19:04:43 +02:00
|
|
|
|
objBefore,
|
2023-09-05 20:11:33 +02:00
|
|
|
|
rawPrunePaths,
|
|
|
|
|
rawNeedlePaths,
|
2023-10-05 17:24:35 +02:00
|
|
|
|
stackNeedle,
|
2023-09-05 20:11:33 +02:00
|
|
|
|
extraArgs
|
|
|
|
|
);
|
2023-09-06 19:04:43 +02:00
|
|
|
|
let outerResponse;
|
|
|
|
|
if ( typeof objAfter === 'object' ) {
|
|
|
|
|
outerResponse = typeof innerResponse === 'string'
|
2023-10-17 23:33:49 +02:00
|
|
|
|
? safe.JSON_stringify(objAfter)
|
2023-09-06 19:04:43 +02:00
|
|
|
|
: objAfter;
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'Pruned');
|
2023-09-06 19:04:43 +02:00
|
|
|
|
} else {
|
2023-09-05 20:11:33 +02:00
|
|
|
|
outerResponse = innerResponse;
|
|
|
|
|
}
|
2023-09-06 19:04:43 +02:00
|
|
|
|
return (xhrDetails.response = outerResponse);
|
|
|
|
|
}
|
|
|
|
|
get responseText() {
|
|
|
|
|
const response = this.response;
|
|
|
|
|
return typeof response !== 'string'
|
|
|
|
|
? super.responseText
|
|
|
|
|
: response;
|
2023-09-04 20:54:57 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-05-29 20:37:02 +02:00
|
|
|
|
// There is still code out there which uses `eval` in lieu of `JSON.parse`.
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'evaldata-prune.js',
|
|
|
|
|
fn: evaldataPrune,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'object-prune.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function evaldataPrune(
|
|
|
|
|
rawPrunePaths = '',
|
|
|
|
|
rawNeedlePaths = ''
|
|
|
|
|
) {
|
|
|
|
|
self.eval = new Proxy(self.eval, {
|
|
|
|
|
apply(target, thisArg, args) {
|
2023-08-09 17:05:53 +02:00
|
|
|
|
const before = Reflect.apply(target, thisArg, args);
|
|
|
|
|
if ( typeof before === 'object' ) {
|
2023-10-17 23:33:49 +02:00
|
|
|
|
const after = objectPruneFn(before, rawPrunePaths, rawNeedlePaths);
|
2023-08-09 17:05:53 +02:00
|
|
|
|
return after || before;
|
2023-05-29 20:37:02 +02:00
|
|
|
|
}
|
2023-08-09 17:05:53 +02:00
|
|
|
|
return before;
|
2023-05-29 20:37:02 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
2023-09-28 16:07:03 +02:00
|
|
|
|
name: 'adjust-setInterval.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
2023-09-28 16:07:03 +02:00
|
|
|
|
'nano-setInterval-booster.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
'nano-sib.js',
|
|
|
|
|
],
|
2023-09-28 16:07:03 +02:00
|
|
|
|
fn: adjustSetInterval,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
2019-07-10 14:11:51 +02:00
|
|
|
|
// Imported from:
|
|
|
|
|
// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L126
|
|
|
|
|
//
|
|
|
|
|
// Speed up or down setInterval, 3 optional arguments.
|
|
|
|
|
// The payload matcher, a string literal or a JavaScript RegExp, defaults
|
|
|
|
|
// to match all.
|
|
|
|
|
// delayMatcher
|
|
|
|
|
// The delay matcher, an integer, defaults to 1000.
|
2021-01-23 15:45:44 +01:00
|
|
|
|
// Use `*` to match any delay.
|
2019-07-10 14:11:51 +02:00
|
|
|
|
// boostRatio - The delay multiplier when there is a match, 0.5 speeds up by
|
|
|
|
|
// 2 times and 2 slows down by 2 times, defaults to 0.05 or speed up
|
|
|
|
|
// 20 times. Speed up and down both cap at 50 times.
|
2023-09-28 16:07:03 +02:00
|
|
|
|
function adjustSetInterval(
|
2023-03-24 19:05:18 +01:00
|
|
|
|
needleArg = '',
|
|
|
|
|
delayArg = '',
|
|
|
|
|
boostArg = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof needleArg !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const reNeedle = safe.patternToRegex(needleArg);
|
2021-01-23 15:45:44 +01:00
|
|
|
|
let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1;
|
|
|
|
|
if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; }
|
|
|
|
|
let boost = parseFloat(boostArg);
|
|
|
|
|
boost = isNaN(boost) === false && isFinite(boost)
|
2023-09-14 17:13:58 +02:00
|
|
|
|
? Math.min(Math.max(boost, 0.001), 50)
|
2021-01-23 15:45:44 +01:00
|
|
|
|
: 0.05;
|
|
|
|
|
self.setInterval = new Proxy(self.setInterval, {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
apply: function(target, thisArg, args) {
|
2021-01-23 15:45:44 +01:00
|
|
|
|
const [ a, b ] = args;
|
|
|
|
|
if (
|
|
|
|
|
(delay === -1 || b === delay) &&
|
|
|
|
|
reNeedle.test(a.toString())
|
|
|
|
|
) {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
args[1] = b * boost;
|
|
|
|
|
}
|
|
|
|
|
return target.apply(thisArg, args);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
2023-09-28 16:07:03 +02:00
|
|
|
|
name: 'adjust-setTimeout.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
2023-09-28 16:07:03 +02:00
|
|
|
|
'nano-setTimeout-booster.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
'nano-stb.js',
|
|
|
|
|
],
|
2023-09-28 16:07:03 +02:00
|
|
|
|
fn: adjustSetTimeout,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
2019-07-10 14:11:51 +02:00
|
|
|
|
// Imported from:
|
|
|
|
|
// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L82
|
|
|
|
|
//
|
|
|
|
|
// Speed up or down setTimeout, 3 optional arguments.
|
|
|
|
|
// funcMatcher
|
|
|
|
|
// The payload matcher, a string literal or a JavaScript RegExp, defaults
|
|
|
|
|
// to match all.
|
|
|
|
|
// delayMatcher
|
|
|
|
|
// The delay matcher, an integer, defaults to 1000.
|
2021-01-23 15:45:44 +01:00
|
|
|
|
// Use `*` to match any delay.
|
2019-07-10 14:11:51 +02:00
|
|
|
|
// boostRatio - The delay multiplier when there is a match, 0.5 speeds up by
|
|
|
|
|
// 2 times and 2 slows down by 2 times, defaults to 0.05 or speed up
|
|
|
|
|
// 20 times. Speed up and down both cap at 50 times.
|
2023-09-28 16:07:03 +02:00
|
|
|
|
function adjustSetTimeout(
|
2023-03-24 19:05:18 +01:00
|
|
|
|
needleArg = '',
|
|
|
|
|
delayArg = '',
|
|
|
|
|
boostArg = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof needleArg !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const reNeedle = safe.patternToRegex(needleArg);
|
2021-01-23 15:45:44 +01:00
|
|
|
|
let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1;
|
|
|
|
|
if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; }
|
|
|
|
|
let boost = parseFloat(boostArg);
|
|
|
|
|
boost = isNaN(boost) === false && isFinite(boost)
|
2023-09-14 17:13:58 +02:00
|
|
|
|
? Math.min(Math.max(boost, 0.001), 50)
|
2021-01-23 15:45:44 +01:00
|
|
|
|
: 0.05;
|
|
|
|
|
self.setTimeout = new Proxy(self.setTimeout, {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
apply: function(target, thisArg, args) {
|
2021-01-23 15:45:44 +01:00
|
|
|
|
const [ a, b ] = args;
|
|
|
|
|
if (
|
|
|
|
|
(delay === -1 || b === delay) &&
|
|
|
|
|
reNeedle.test(a.toString())
|
|
|
|
|
) {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
args[1] = b * boost;
|
|
|
|
|
}
|
|
|
|
|
return target.apply(thisArg, args);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'noeval-if.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'prevent-eval-if.js',
|
|
|
|
|
],
|
2023-03-26 15:13:17 +02:00
|
|
|
|
fn: noEvalIf,
|
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
2023-03-26 15:13:17 +02:00
|
|
|
|
function noEvalIf(
|
2023-03-24 19:05:18 +01:00
|
|
|
|
needle = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof needle !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-05-05 17:46:16 +02:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('noeval-if', needle);
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const reNeedle = safe.patternToRegex(needle);
|
2023-03-26 15:13:17 +02:00
|
|
|
|
window.eval = new Proxy(window.eval, { // jshint ignore: line
|
2019-07-06 18:36:28 +02:00
|
|
|
|
apply: function(target, thisArg, args) {
|
2024-05-05 17:46:16 +02:00
|
|
|
|
const a = String(args[0]);
|
|
|
|
|
if ( needle !== '' && reNeedle.test(a) ) {
|
|
|
|
|
safe.uboLog(logPrefix, 'Prevented:\n', a);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( needle === '' || safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, 'Not prevented:\n', a);
|
|
|
|
|
}
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
2024-01-25 18:20:38 +01:00
|
|
|
|
name: 'prevent-fetch.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
2024-01-25 18:20:38 +01:00
|
|
|
|
'no-fetch-if.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: noFetchIf,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-11-25 17:13:57 +01:00
|
|
|
|
'generate-content.fn',
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function noFetchIf(
|
2023-11-25 17:13:57 +01:00
|
|
|
|
propsToMatch = '',
|
2024-01-01 16:24:47 +01:00
|
|
|
|
responseBody = ''
|
2023-03-24 19:05:18 +01:00
|
|
|
|
) {
|
2023-11-25 17:13:57 +01:00
|
|
|
|
if ( typeof propsToMatch !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody);
|
2020-12-11 14:29:23 +01:00
|
|
|
|
const needles = [];
|
2023-11-25 17:13:57 +01:00
|
|
|
|
for ( const condition of propsToMatch.split(/\s+/) ) {
|
2020-12-11 15:28:29 +01:00
|
|
|
|
if ( condition === '' ) { continue; }
|
2020-12-11 14:29:23 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
2023-08-08 13:41:21 +02:00
|
|
|
|
needles.push({ key, re: safe.patternToRegex(value) });
|
2020-12-11 14:29:23 +01:00
|
|
|
|
}
|
|
|
|
|
self.fetch = new Proxy(self.fetch, {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
2023-11-25 17:13:57 +01:00
|
|
|
|
const details = args[0] instanceof self.Request
|
|
|
|
|
? args[0]
|
|
|
|
|
: Object.assign({ url: args[0] }, args[1]);
|
2020-12-11 14:29:23 +01:00
|
|
|
|
let proceed = true;
|
|
|
|
|
try {
|
2020-12-24 14:26:30 +01:00
|
|
|
|
const props = new Map();
|
|
|
|
|
for ( const prop in details ) {
|
|
|
|
|
let v = details[prop];
|
|
|
|
|
if ( typeof v !== 'string' ) {
|
2024-01-28 16:58:41 +01:00
|
|
|
|
try { v = safe.JSON_stringify(v); }
|
2020-12-24 14:26:30 +01:00
|
|
|
|
catch(ex) { }
|
2020-12-11 14:29:23 +01:00
|
|
|
|
}
|
2020-12-24 14:26:30 +01:00
|
|
|
|
if ( typeof v !== 'string' ) { continue; }
|
|
|
|
|
props.set(prop, v);
|
2020-12-11 14:29:23 +01:00
|
|
|
|
}
|
2024-02-13 22:12:11 +01:00
|
|
|
|
if ( propsToMatch === '' && responseBody === '' ) {
|
|
|
|
|
const out = Array.from(props).map(a => `${a[0]}:${a[1]}`);
|
|
|
|
|
safe.uboLog(logPrefix, `Called: ${out.join('\n')}`);
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
2020-12-11 15:28:29 +01:00
|
|
|
|
proceed = needles.length === 0;
|
2020-12-11 14:29:23 +01:00
|
|
|
|
for ( const { key, re } of needles ) {
|
|
|
|
|
if (
|
|
|
|
|
props.has(key) === false ||
|
|
|
|
|
re.test(props.get(key)) === false
|
|
|
|
|
) {
|
|
|
|
|
proceed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
}
|
2023-11-25 17:13:57 +01:00
|
|
|
|
if ( proceed ) {
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
2024-01-01 16:24:47 +01:00
|
|
|
|
let responseType = '';
|
|
|
|
|
if ( details.mode === undefined || details.mode === 'cors' ) {
|
|
|
|
|
try {
|
|
|
|
|
const desURL = new URL(details.url);
|
|
|
|
|
responseType = desURL.origin !== document.location.origin
|
|
|
|
|
? 'cors'
|
|
|
|
|
: 'basic';
|
2024-01-25 18:20:38 +01:00
|
|
|
|
} catch(ex) {
|
|
|
|
|
safe.uboErr(logPrefix, `Error: ${ex}`);
|
2024-01-01 16:24:47 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return generateContentFn(responseBody).then(text => {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Prevented with response "${text}"`);
|
2023-11-25 17:13:57 +01:00
|
|
|
|
const response = new Response(text, {
|
|
|
|
|
statusText: 'OK',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Length': text.length,
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-01-11 17:41:37 +01:00
|
|
|
|
safe.Object_defineProperty(response, 'url', {
|
2023-11-25 17:13:57 +01:00
|
|
|
|
value: details.url
|
|
|
|
|
});
|
2024-01-01 16:24:47 +01:00
|
|
|
|
if ( responseType !== '' ) {
|
2024-01-11 17:41:37 +01:00
|
|
|
|
safe.Object_defineProperty(response, 'type', {
|
2024-01-01 16:24:47 +01:00
|
|
|
|
value: responseType
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-11-25 17:13:57 +01:00
|
|
|
|
return response;
|
|
|
|
|
});
|
2020-12-11 14:29:23 +01:00
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2021-04-11 13:11:09 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2021-04-11 13:11:09 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
2023-09-28 16:07:03 +02:00
|
|
|
|
name: 'prevent-refresh.js',
|
|
|
|
|
aliases: [
|
|
|
|
|
'refresh-defuser.js',
|
|
|
|
|
],
|
|
|
|
|
fn: preventRefresh,
|
2023-06-16 01:57:10 +02:00
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
|
|
|
|
'run-at.fn',
|
2024-01-25 18:20:38 +01:00
|
|
|
|
'safe-self.fn',
|
2023-06-16 01:57:10 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
2021-10-03 15:46:24 +02:00
|
|
|
|
// https://www.reddit.com/r/uBlockOrigin/comments/q0frv0/while_reading_a_sports_article_i_was_redirected/hf7wo9v/
|
2023-09-28 16:07:03 +02:00
|
|
|
|
function preventRefresh(
|
2023-03-24 19:05:18 +01:00
|
|
|
|
arg1 = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof arg1 !== 'string' ) { return; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('prevent-refresh', arg1);
|
2021-10-03 15:46:24 +02:00
|
|
|
|
const defuse = ( ) => {
|
|
|
|
|
const meta = document.querySelector('meta[http-equiv="refresh" i][content]');
|
|
|
|
|
if ( meta === null ) { return; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Prevented "${meta.textContent}"`);
|
2023-03-24 19:05:18 +01:00
|
|
|
|
const s = arg1 === ''
|
2021-10-03 15:46:24 +02:00
|
|
|
|
? meta.getAttribute('content')
|
|
|
|
|
: arg1;
|
|
|
|
|
const ms = Math.max(parseFloat(s) || 0, 0) * 1000;
|
|
|
|
|
setTimeout(( ) => { window.stop(); }, ms);
|
|
|
|
|
};
|
2023-06-16 01:57:10 +02:00
|
|
|
|
runAt(( ) => {
|
2021-10-03 15:46:24 +02:00
|
|
|
|
defuse();
|
2023-06-16 01:57:10 +02:00
|
|
|
|
}, 'interactive');
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2021-10-03 15:46:24 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2021-10-03 15:46:24 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'remove-attr.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'ra.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: removeAttr,
|
2023-06-16 01:57:10 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'run-at.fn',
|
2024-04-17 15:17:49 +02:00
|
|
|
|
'safe-self.fn',
|
2023-06-16 01:57:10 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function removeAttr(
|
2024-04-17 15:17:49 +02:00
|
|
|
|
rawToken = '',
|
|
|
|
|
rawSelector = '',
|
2023-03-24 19:05:18 +01:00
|
|
|
|
behavior = ''
|
|
|
|
|
) {
|
2024-04-17 15:17:49 +02:00
|
|
|
|
if ( typeof rawToken !== 'string' ) { return; }
|
|
|
|
|
if ( rawToken === '' ) { return; }
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('remove-attr', rawToken, rawSelector, behavior);
|
|
|
|
|
const tokens = rawToken.split(/\s*\|\s*/);
|
|
|
|
|
const selector = tokens
|
|
|
|
|
.map(a => `${rawSelector}[${CSS.escape(a)}]`)
|
|
|
|
|
.join(',');
|
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Target selector:\n\t${selector}`);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
}
|
2021-01-08 16:45:35 +01:00
|
|
|
|
let timer;
|
|
|
|
|
const rmattr = ( ) => {
|
|
|
|
|
timer = undefined;
|
2019-07-06 18:36:28 +02:00
|
|
|
|
try {
|
|
|
|
|
const nodes = document.querySelectorAll(selector);
|
|
|
|
|
for ( const node of nodes ) {
|
|
|
|
|
for ( const attr of tokens ) {
|
2024-04-17 15:17:49 +02:00
|
|
|
|
if ( node.hasAttribute(attr) === false ) { continue; }
|
2019-07-06 18:36:28 +02:00
|
|
|
|
node.removeAttribute(attr);
|
2024-04-17 15:17:49 +02:00
|
|
|
|
safe.uboLog(logPrefix, `Removed attribute '${attr}'`);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
}
|
|
|
|
|
};
|
2021-01-08 16:45:35 +01:00
|
|
|
|
const mutationHandler = mutations => {
|
|
|
|
|
if ( timer !== undefined ) { return; }
|
|
|
|
|
let skip = true;
|
|
|
|
|
for ( let i = 0; i < mutations.length && skip; i++ ) {
|
|
|
|
|
const { type, addedNodes, removedNodes } = mutations[i];
|
|
|
|
|
if ( type === 'attributes' ) { skip = false; }
|
|
|
|
|
for ( let j = 0; j < addedNodes.length && skip; j++ ) {
|
|
|
|
|
if ( addedNodes[j].nodeType === 1 ) { skip = false; break; }
|
|
|
|
|
}
|
|
|
|
|
for ( let j = 0; j < removedNodes.length && skip; j++ ) {
|
|
|
|
|
if ( removedNodes[j].nodeType === 1 ) { skip = false; break; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( skip ) { return; }
|
2024-04-17 15:17:49 +02:00
|
|
|
|
timer = self.requestIdleCallback(rmattr, { timeout: 67 });
|
2021-01-08 16:45:35 +01:00
|
|
|
|
};
|
|
|
|
|
const start = ( ) => {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
rmattr();
|
2021-01-08 16:45:35 +01:00
|
|
|
|
if ( /\bstay\b/.test(behavior) === false ) { return; }
|
|
|
|
|
const observer = new MutationObserver(mutationHandler);
|
2021-10-14 15:08:08 +02:00
|
|
|
|
observer.observe(document, {
|
2021-01-08 16:45:35 +01:00
|
|
|
|
attributes: true,
|
|
|
|
|
attributeFilter: tokens,
|
|
|
|
|
childList: true,
|
|
|
|
|
subtree: true,
|
|
|
|
|
});
|
|
|
|
|
};
|
2023-06-16 01:57:10 +02:00
|
|
|
|
runAt(( ) => {
|
2021-01-08 16:45:35 +01:00
|
|
|
|
start();
|
2023-06-16 01:57:10 +02:00
|
|
|
|
}, /\bcomplete\b/.test(behavior) ? 'idle' : 'interactive');
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'remove-class.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'rc.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: removeClass,
|
2023-07-24 13:33:33 +02:00
|
|
|
|
world: 'ISOLATED',
|
2023-06-16 01:57:10 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'run-at.fn',
|
2024-04-17 15:17:49 +02:00
|
|
|
|
'safe-self.fn',
|
2023-06-16 01:57:10 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function removeClass(
|
2024-04-17 15:17:49 +02:00
|
|
|
|
rawToken = '',
|
|
|
|
|
rawSelector = '',
|
2023-03-24 19:05:18 +01:00
|
|
|
|
behavior = ''
|
|
|
|
|
) {
|
2024-04-17 15:17:49 +02:00
|
|
|
|
if ( typeof rawToken !== 'string' ) { return; }
|
|
|
|
|
if ( rawToken === '' ) { return; }
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('remove-class', rawToken, rawSelector, behavior);
|
|
|
|
|
const tokens = rawToken.split(/\s*\|\s*/);
|
|
|
|
|
const selector = tokens
|
|
|
|
|
.map(a => `${rawSelector}.${CSS.escape(a)}`)
|
|
|
|
|
.join(',');
|
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Target selector:\n\t${selector}`);
|
2020-03-18 14:44:18 +01:00
|
|
|
|
}
|
2023-07-24 13:33:33 +02:00
|
|
|
|
const mustStay = /\bstay\b/.test(behavior);
|
2021-06-06 14:58:40 +02:00
|
|
|
|
let timer;
|
2024-04-17 15:17:49 +02:00
|
|
|
|
const rmclass = ( ) => {
|
2021-06-06 14:58:40 +02:00
|
|
|
|
timer = undefined;
|
2020-03-18 14:44:18 +01:00
|
|
|
|
try {
|
|
|
|
|
const nodes = document.querySelectorAll(selector);
|
|
|
|
|
for ( const node of nodes ) {
|
2024-04-17 15:17:49 +02:00
|
|
|
|
node.classList.remove(...tokens);
|
|
|
|
|
safe.uboLog(logPrefix, 'Removed class(es)');
|
2020-03-18 14:44:18 +01:00
|
|
|
|
}
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
}
|
2023-07-24 13:33:33 +02:00
|
|
|
|
if ( mustStay ) { return; }
|
|
|
|
|
if ( document.readyState !== 'complete' ) { return; }
|
|
|
|
|
observer.disconnect();
|
2020-03-18 14:44:18 +01:00
|
|
|
|
};
|
2021-06-06 14:58:40 +02:00
|
|
|
|
const mutationHandler = mutations => {
|
|
|
|
|
if ( timer !== undefined ) { return; }
|
|
|
|
|
let skip = true;
|
|
|
|
|
for ( let i = 0; i < mutations.length && skip; i++ ) {
|
|
|
|
|
const { type, addedNodes, removedNodes } = mutations[i];
|
|
|
|
|
if ( type === 'attributes' ) { skip = false; }
|
|
|
|
|
for ( let j = 0; j < addedNodes.length && skip; j++ ) {
|
|
|
|
|
if ( addedNodes[j].nodeType === 1 ) { skip = false; break; }
|
|
|
|
|
}
|
|
|
|
|
for ( let j = 0; j < removedNodes.length && skip; j++ ) {
|
|
|
|
|
if ( removedNodes[j].nodeType === 1 ) { skip = false; break; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( skip ) { return; }
|
|
|
|
|
timer = self.requestIdleCallback(rmclass, { timeout: 67 });
|
|
|
|
|
};
|
2023-07-24 13:33:33 +02:00
|
|
|
|
const observer = new MutationObserver(mutationHandler);
|
2021-06-06 14:58:40 +02:00
|
|
|
|
const start = ( ) => {
|
2020-03-18 14:44:18 +01:00
|
|
|
|
rmclass();
|
2021-10-14 15:08:08 +02:00
|
|
|
|
observer.observe(document, {
|
2021-06-06 14:58:40 +02:00
|
|
|
|
attributes: true,
|
|
|
|
|
attributeFilter: [ 'class' ],
|
|
|
|
|
childList: true,
|
|
|
|
|
subtree: true,
|
|
|
|
|
});
|
|
|
|
|
};
|
2023-06-16 01:57:10 +02:00
|
|
|
|
runAt(( ) => {
|
2021-06-06 14:58:40 +02:00
|
|
|
|
start();
|
2023-07-24 13:33:33 +02:00
|
|
|
|
}, /\bcomplete\b/.test(behavior) ? 'idle' : 'loading');
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2020-03-18 14:44:18 +01:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2020-03-18 14:44:18 +01:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'no-requestAnimationFrame-if.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'norafif.js',
|
|
|
|
|
'prevent-requestAnimationFrame.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: noRequestAnimationFrameIf,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function noRequestAnimationFrameIf(
|
|
|
|
|
needle = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof needle !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
2020-04-15 16:06:53 +02:00
|
|
|
|
const needleNot = needle.charAt(0) === '!';
|
|
|
|
|
if ( needleNot ) { needle = needle.slice(1); }
|
|
|
|
|
const log = needleNot === false && needle === '' ? console.log : undefined;
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const reNeedle = safe.patternToRegex(needle);
|
2020-04-15 16:06:53 +02:00
|
|
|
|
window.requestAnimationFrame = new Proxy(window.requestAnimationFrame, {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
2023-11-06 15:10:21 +01:00
|
|
|
|
const a = args[0] instanceof Function
|
|
|
|
|
? String(safe.Function_toString(args[0]))
|
|
|
|
|
: String(args[0]);
|
2020-04-15 16:06:53 +02:00
|
|
|
|
let defuse = false;
|
|
|
|
|
if ( log !== undefined ) {
|
|
|
|
|
log('uBO: requestAnimationFrame("%s")', a);
|
|
|
|
|
} else {
|
|
|
|
|
defuse = reNeedle.test(a) !== needleNot;
|
|
|
|
|
}
|
|
|
|
|
if ( defuse ) {
|
|
|
|
|
args[0] = function(){};
|
|
|
|
|
}
|
|
|
|
|
return target.apply(thisArg, args);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2020-04-15 16:06:53 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2020-04-15 16:06:53 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'set-constant.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'set.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: setConstant,
|
2023-04-27 18:52:17 +02:00
|
|
|
|
dependencies: [
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
'set-constant.fn'
|
2023-04-27 18:52:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function setConstant(
|
2023-05-23 16:59:27 +02:00
|
|
|
|
...args
|
2023-03-24 19:05:18 +01:00
|
|
|
|
) {
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
setConstantFn(false, ...args);
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'no-setInterval-if.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'nosiif.js',
|
|
|
|
|
'prevent-setInterval.js',
|
2023-07-12 00:25:21 +02:00
|
|
|
|
'setInterval-defuser.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: noSetIntervalIf,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function noSetIntervalIf(
|
|
|
|
|
needle = '',
|
|
|
|
|
delay = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof needle !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('prevent-setInterval', needle, delay);
|
2019-08-24 19:54:31 +02:00
|
|
|
|
const needleNot = needle.charAt(0) === '!';
|
|
|
|
|
if ( needleNot ) { needle = needle.slice(1); }
|
2023-03-24 19:05:18 +01:00
|
|
|
|
if ( delay === '' ) { delay = undefined; }
|
2020-07-02 17:47:49 +02:00
|
|
|
|
let delayNot = false;
|
|
|
|
|
if ( delay !== undefined ) {
|
|
|
|
|
delayNot = delay.charAt(0) === '!';
|
|
|
|
|
if ( delayNot ) { delay = delay.slice(1); }
|
|
|
|
|
delay = parseInt(delay, 10);
|
|
|
|
|
}
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const reNeedle = safe.patternToRegex(needle);
|
2023-07-12 00:25:21 +02:00
|
|
|
|
self.setInterval = new Proxy(self.setInterval, {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
apply: function(target, thisArg, args) {
|
2023-11-06 15:10:21 +01:00
|
|
|
|
const a = args[0] instanceof Function
|
|
|
|
|
? String(safe.Function_toString(args[0]))
|
|
|
|
|
: String(args[0]);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
const b = args[1];
|
2024-01-28 16:27:46 +01:00
|
|
|
|
if ( needle === '' && delay === undefined ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
let defuse;
|
|
|
|
|
if ( needle !== '' ) {
|
|
|
|
|
defuse = reNeedle.test(a) !== needleNot;
|
|
|
|
|
}
|
|
|
|
|
if ( defuse !== false && delay !== undefined ) {
|
|
|
|
|
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
|
|
|
|
|
}
|
|
|
|
|
if ( defuse ) {
|
|
|
|
|
args[0] = function(){};
|
|
|
|
|
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
|
2019-08-22 15:32:46 +02:00
|
|
|
|
}
|
2023-07-12 00:25:21 +02:00
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
2023-05-19 18:55:01 +02:00
|
|
|
|
},
|
|
|
|
|
get(target, prop, receiver) {
|
|
|
|
|
if ( prop === 'toString' ) {
|
|
|
|
|
return target.toString.bind(target);
|
|
|
|
|
}
|
|
|
|
|
return Reflect.get(target, prop, receiver);
|
|
|
|
|
},
|
2019-07-06 18:36:28 +02:00
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'no-setTimeout-if.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'nostif.js',
|
|
|
|
|
'prevent-setTimeout.js',
|
|
|
|
|
'setTimeout-defuser.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: noSetTimeoutIf,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function noSetTimeoutIf(
|
|
|
|
|
needle = '',
|
|
|
|
|
delay = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof needle !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('prevent-setTimeout', needle, delay);
|
2019-08-24 19:54:31 +02:00
|
|
|
|
const needleNot = needle.charAt(0) === '!';
|
|
|
|
|
if ( needleNot ) { needle = needle.slice(1); }
|
2023-03-24 19:05:18 +01:00
|
|
|
|
if ( delay === '' ) { delay = undefined; }
|
2020-07-02 17:47:49 +02:00
|
|
|
|
let delayNot = false;
|
|
|
|
|
if ( delay !== undefined ) {
|
|
|
|
|
delayNot = delay.charAt(0) === '!';
|
|
|
|
|
if ( delayNot ) { delay = delay.slice(1); }
|
|
|
|
|
delay = parseInt(delay, 10);
|
|
|
|
|
}
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const reNeedle = safe.patternToRegex(needle);
|
2023-07-12 00:25:21 +02:00
|
|
|
|
self.setTimeout = new Proxy(self.setTimeout, {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
apply: function(target, thisArg, args) {
|
2023-11-06 15:10:21 +01:00
|
|
|
|
const a = args[0] instanceof Function
|
|
|
|
|
? String(safe.Function_toString(args[0]))
|
|
|
|
|
: String(args[0]);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
const b = args[1];
|
2024-01-28 16:27:46 +01:00
|
|
|
|
if ( needle === '' && delay === undefined ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
let defuse;
|
|
|
|
|
if ( needle !== '' ) {
|
|
|
|
|
defuse = reNeedle.test(a) !== needleNot;
|
|
|
|
|
}
|
|
|
|
|
if ( defuse !== false && delay !== undefined ) {
|
|
|
|
|
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
|
|
|
|
|
}
|
|
|
|
|
if ( defuse ) {
|
|
|
|
|
args[0] = function(){};
|
|
|
|
|
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
|
2019-08-22 15:32:46 +02:00
|
|
|
|
}
|
2023-07-12 00:25:21 +02:00
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
2023-05-19 18:55:01 +02:00
|
|
|
|
},
|
|
|
|
|
get(target, prop, receiver) {
|
|
|
|
|
if ( prop === 'toString' ) {
|
|
|
|
|
return target.toString.bind(target);
|
|
|
|
|
}
|
|
|
|
|
return Reflect.get(target, prop, receiver);
|
|
|
|
|
},
|
2019-07-06 18:36:28 +02:00
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'webrtc-if.js',
|
|
|
|
|
fn: webrtcIf,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function webrtcIf(
|
|
|
|
|
good = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof good !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const reGood = safe.patternToRegex(good);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
const rtcName = window.RTCPeerConnection
|
|
|
|
|
? 'RTCPeerConnection'
|
|
|
|
|
: (window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : '');
|
|
|
|
|
if ( rtcName === '' ) { return; }
|
|
|
|
|
const log = console.log.bind(console);
|
|
|
|
|
const neuteredPeerConnections = new WeakSet();
|
|
|
|
|
const isGoodConfig = function(instance, config) {
|
|
|
|
|
if ( neuteredPeerConnections.has(instance) ) { return false; }
|
|
|
|
|
if ( config instanceof Object === false ) { return true; }
|
|
|
|
|
if ( Array.isArray(config.iceServers) === false ) { return true; }
|
|
|
|
|
for ( const server of config.iceServers ) {
|
|
|
|
|
const urls = typeof server.urls === 'string'
|
|
|
|
|
? [ server.urls ]
|
|
|
|
|
: server.urls;
|
|
|
|
|
if ( Array.isArray(urls) ) {
|
|
|
|
|
for ( const url of urls ) {
|
|
|
|
|
if ( reGood.test(url) ) { return true; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( typeof server.username === 'string' ) {
|
|
|
|
|
if ( reGood.test(server.username) ) { return true; }
|
|
|
|
|
}
|
|
|
|
|
if ( typeof server.credential === 'string' ) {
|
|
|
|
|
if ( reGood.test(server.credential) ) { return true; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
neuteredPeerConnections.add(instance);
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
const peerConnectionCtor = window[rtcName];
|
|
|
|
|
const peerConnectionProto = peerConnectionCtor.prototype;
|
|
|
|
|
peerConnectionProto.createDataChannel =
|
|
|
|
|
new Proxy(peerConnectionProto.createDataChannel, {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
|
if ( isGoodConfig(target, args[1]) === false ) {
|
2020-04-28 17:19:26 +02:00
|
|
|
|
log('uBO:', args[1]);
|
2019-07-11 15:45:53 +02:00
|
|
|
|
return Reflect.apply(target, thisArg, args.slice(0, 1));
|
2019-07-06 18:36:28 +02:00
|
|
|
|
}
|
2019-07-11 15:45:53 +02:00
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
window[rtcName] =
|
|
|
|
|
new Proxy(peerConnectionCtor, {
|
|
|
|
|
construct: function(target, args) {
|
|
|
|
|
if ( isGoodConfig(target, args[0]) === false ) {
|
2020-04-28 17:19:26 +02:00
|
|
|
|
log('uBO:', args[0]);
|
2019-07-11 15:45:53 +02:00
|
|
|
|
return Reflect.construct(target);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
}
|
2019-07-11 15:45:53 +02:00
|
|
|
|
return Reflect.construct(target, args);
|
2019-07-06 18:36:28 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'no-xhr-if.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'prevent-xhr.js',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: noXhrIf,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-11-25 17:13:57 +01:00
|
|
|
|
'generate-content.fn',
|
2023-08-13 19:23:41 +02:00
|
|
|
|
'match-object-properties.fn',
|
|
|
|
|
'parse-properties-to-match.fn',
|
2023-08-20 01:21:22 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function noXhrIf(
|
2023-08-13 19:23:41 +02:00
|
|
|
|
propsToMatch = '',
|
2023-08-14 16:03:50 +02:00
|
|
|
|
directive = ''
|
2023-03-24 19:05:18 +01:00
|
|
|
|
) {
|
2023-08-13 19:23:41 +02:00
|
|
|
|
if ( typeof propsToMatch !== 'string' ) { return; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('prevent-xhr', propsToMatch, directive);
|
2021-09-11 15:15:39 +02:00
|
|
|
|
const xhrInstances = new WeakMap();
|
2023-08-13 19:23:41 +02:00
|
|
|
|
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const warOrigin = scriptletGlobals.warOrigin;
|
2023-12-10 21:21:29 +01:00
|
|
|
|
const headers = {
|
|
|
|
|
'date': '',
|
|
|
|
|
'content-type': '',
|
|
|
|
|
'content-length': '',
|
|
|
|
|
};
|
2021-09-11 15:15:39 +02:00
|
|
|
|
self.XMLHttpRequest = class extends self.XMLHttpRequest {
|
2023-08-13 19:23:41 +02:00
|
|
|
|
open(method, url, ...args) {
|
2023-12-10 21:21:29 +01:00
|
|
|
|
xhrInstances.delete(this);
|
2023-08-14 16:03:50 +02:00
|
|
|
|
if ( warOrigin !== undefined && url.startsWith(warOrigin) ) {
|
|
|
|
|
return super.open(method, url, ...args);
|
|
|
|
|
}
|
|
|
|
|
const haystack = { method, url };
|
2024-01-28 16:58:41 +01:00
|
|
|
|
if ( propsToMatch === '' && directive === '' ) {
|
2024-01-29 00:47:37 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Called: ${safe.JSON_stringify(haystack, null, 2)}`);
|
2024-01-31 02:52:07 +01:00
|
|
|
|
return super.open(method, url, ...args);
|
|
|
|
|
}
|
|
|
|
|
if ( matchObjectProperties(propNeedles, haystack) ) {
|
|
|
|
|
xhrInstances.set(this, haystack);
|
2024-01-28 16:58:41 +01:00
|
|
|
|
}
|
2023-12-10 21:21:29 +01:00
|
|
|
|
haystack.headers = Object.assign({}, headers);
|
2023-08-13 19:23:41 +02:00
|
|
|
|
return super.open(method, url, ...args);
|
2021-09-11 15:15:39 +02:00
|
|
|
|
}
|
|
|
|
|
send(...args) {
|
|
|
|
|
const haystack = xhrInstances.get(this);
|
|
|
|
|
if ( haystack === undefined ) {
|
|
|
|
|
return super.send(...args);
|
|
|
|
|
}
|
2023-12-10 21:21:29 +01:00
|
|
|
|
haystack.headers['date'] = (new Date()).toUTCString();
|
2023-08-14 16:03:50 +02:00
|
|
|
|
let promise = Promise.resolve({
|
|
|
|
|
xhr: this,
|
|
|
|
|
directive,
|
|
|
|
|
props: {
|
|
|
|
|
readyState: { value: 4 },
|
|
|
|
|
response: { value: '' },
|
|
|
|
|
responseText: { value: '' },
|
|
|
|
|
responseXML: { value: null },
|
|
|
|
|
responseURL: { value: haystack.url },
|
|
|
|
|
status: { value: 200 },
|
|
|
|
|
statusText: { value: 'OK' },
|
|
|
|
|
},
|
2023-08-13 19:23:41 +02:00
|
|
|
|
});
|
|
|
|
|
switch ( this.responseType ) {
|
2023-12-10 21:21:29 +01:00
|
|
|
|
case 'arraybuffer':
|
|
|
|
|
promise = promise.then(details => {
|
|
|
|
|
details.props.response.value = new ArrayBuffer(0);
|
|
|
|
|
return details;
|
|
|
|
|
});
|
|
|
|
|
haystack.headers['content-type'] = 'application/octet-stream';
|
|
|
|
|
break;
|
|
|
|
|
case 'blob':
|
|
|
|
|
promise = promise.then(details => {
|
|
|
|
|
details.props.response.value = new Blob([]);
|
|
|
|
|
return details;
|
|
|
|
|
});
|
|
|
|
|
haystack.headers['content-type'] = 'application/octet-stream';
|
|
|
|
|
break;
|
|
|
|
|
case 'document': {
|
|
|
|
|
promise = promise.then(details => {
|
|
|
|
|
const parser = new DOMParser();
|
|
|
|
|
const doc = parser.parseFromString('', 'text/html');
|
|
|
|
|
details.props.response.value = doc;
|
|
|
|
|
details.props.responseXML.value = doc;
|
|
|
|
|
return details;
|
|
|
|
|
});
|
|
|
|
|
haystack.headers['content-type'] = 'text/html';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'json':
|
|
|
|
|
promise = promise.then(details => {
|
|
|
|
|
details.props.response.value = {};
|
|
|
|
|
details.props.responseText.value = '{}';
|
|
|
|
|
return details;
|
|
|
|
|
});
|
|
|
|
|
haystack.headers['content-type'] = 'application/json';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if ( directive === '' ) { break; }
|
|
|
|
|
promise = promise.then(details => {
|
|
|
|
|
return generateContentFn(details.directive).then(text => {
|
|
|
|
|
details.props.response.value = text;
|
|
|
|
|
details.props.responseText.value = text;
|
2023-08-14 16:03:50 +02:00
|
|
|
|
return details;
|
|
|
|
|
});
|
2023-12-10 21:21:29 +01:00
|
|
|
|
});
|
|
|
|
|
haystack.headers['content-type'] = 'text/plain';
|
|
|
|
|
break;
|
2023-08-13 19:23:41 +02:00
|
|
|
|
}
|
2023-08-14 16:03:50 +02:00
|
|
|
|
promise.then(details => {
|
2023-12-10 21:21:29 +01:00
|
|
|
|
haystack.headers['content-length'] = `${details.props.response.value}`.length;
|
2023-08-14 16:03:50 +02:00
|
|
|
|
Object.defineProperties(details.xhr, details.props);
|
|
|
|
|
details.xhr.dispatchEvent(new Event('readystatechange'));
|
|
|
|
|
details.xhr.dispatchEvent(new Event('load'));
|
|
|
|
|
details.xhr.dispatchEvent(new Event('loadend'));
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`);
|
2021-09-11 15:15:39 +02:00
|
|
|
|
});
|
|
|
|
|
}
|
2023-12-10 21:21:29 +01:00
|
|
|
|
getResponseHeader(headerName) {
|
|
|
|
|
const haystack = xhrInstances.get(this);
|
|
|
|
|
if ( haystack === undefined || this.readyState < this.HEADERS_RECEIVED ) {
|
|
|
|
|
return super.getResponseHeader(headerName);
|
|
|
|
|
}
|
|
|
|
|
const value = haystack.headers[headerName.toLowerCase()];
|
|
|
|
|
if ( value !== undefined && value !== '' ) { return value; }
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
getAllResponseHeaders() {
|
|
|
|
|
const haystack = xhrInstances.get(this);
|
|
|
|
|
if ( haystack === undefined || this.readyState < this.HEADERS_RECEIVED ) {
|
|
|
|
|
return super.getAllResponseHeaders();
|
|
|
|
|
}
|
|
|
|
|
const out = [];
|
|
|
|
|
for ( const [ name, value ] of Object.entries(haystack.headers) ) {
|
|
|
|
|
if ( !value ) { continue; }
|
|
|
|
|
out.push(`${name}: ${value}`);
|
|
|
|
|
}
|
|
|
|
|
if ( out.length !== 0 ) { out.push(''); }
|
|
|
|
|
return out.join('\r\n');
|
|
|
|
|
}
|
2021-09-11 15:15:39 +02:00
|
|
|
|
};
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2021-09-11 15:15:39 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2021-09-11 15:15:39 +02:00
|
|
|
|
|
2023-06-17 17:53:08 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'no-window-open-if.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'nowoif.js',
|
2023-07-01 16:20:23 +02:00
|
|
|
|
'prevent-window-open.js',
|
2023-07-18 15:39:06 +02:00
|
|
|
|
'window.open-defuser.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
],
|
2023-06-17 17:53:08 +02:00
|
|
|
|
fn: noWindowOpenIf,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function noWindowOpenIf(
|
|
|
|
|
pattern = '',
|
|
|
|
|
delay = '',
|
|
|
|
|
decoy = ''
|
|
|
|
|
) {
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('no-window-open-if', pattern, delay, decoy);
|
2023-06-17 17:53:08 +02:00
|
|
|
|
const targetMatchResult = pattern.startsWith('!') === false;
|
|
|
|
|
if ( targetMatchResult === false ) {
|
|
|
|
|
pattern = pattern.slice(1);
|
|
|
|
|
}
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const rePattern = safe.patternToRegex(pattern);
|
2023-06-17 17:53:08 +02:00
|
|
|
|
let autoRemoveAfter = parseInt(delay);
|
|
|
|
|
if ( isNaN(autoRemoveAfter) ) {
|
|
|
|
|
autoRemoveAfter = -1;
|
|
|
|
|
}
|
|
|
|
|
const createDecoy = function(tag, urlProp, url) {
|
|
|
|
|
const decoyElem = document.createElement(tag);
|
|
|
|
|
decoyElem[urlProp] = url;
|
|
|
|
|
decoyElem.style.setProperty('height','1px', 'important');
|
|
|
|
|
decoyElem.style.setProperty('position','fixed', 'important');
|
|
|
|
|
decoyElem.style.setProperty('top','-1px', 'important');
|
|
|
|
|
decoyElem.style.setProperty('width','1px', 'important');
|
|
|
|
|
document.body.appendChild(decoyElem);
|
|
|
|
|
setTimeout(( ) => { decoyElem.remove(); }, autoRemoveAfter * 1000);
|
|
|
|
|
return decoyElem;
|
|
|
|
|
};
|
|
|
|
|
window.open = new Proxy(window.open, {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
|
const haystack = args.join(' ');
|
|
|
|
|
if ( rePattern.test(haystack) !== targetMatchResult ) {
|
2024-02-02 15:36:08 +01:00
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Allowed (${args.join(', ')})`);
|
|
|
|
|
}
|
2023-06-17 17:53:08 +02:00
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
2024-02-02 15:36:08 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Prevented (${args.join(', ')})`);
|
2023-06-17 17:53:08 +02:00
|
|
|
|
if ( autoRemoveAfter < 0 ) { return null; }
|
|
|
|
|
const decoyElem = decoy === 'obj'
|
|
|
|
|
? createDecoy('object', 'data', ...args)
|
|
|
|
|
: createDecoy('iframe', 'src', ...args);
|
|
|
|
|
let popup = decoyElem.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);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( safe.logLevel !== 0 ) {
|
2023-06-17 17:53:08 +02:00
|
|
|
|
popup = new Proxy(popup, {
|
|
|
|
|
get: function(target, prop) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'window.open / get', prop, '===', target[prop]);
|
2023-06-17 17:53:08 +02:00
|
|
|
|
return Reflect.get(...arguments);
|
|
|
|
|
},
|
|
|
|
|
set: function(target, prop, value) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'window.open / set', prop, '=', value);
|
2023-06-17 17:53:08 +02:00
|
|
|
|
return Reflect.set(...arguments);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return popup;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
2023-09-28 16:07:03 +02:00
|
|
|
|
name: 'close-window.js',
|
|
|
|
|
aliases: [
|
|
|
|
|
'window-close-if.js',
|
|
|
|
|
],
|
|
|
|
|
fn: closeWindow,
|
|
|
|
|
world: 'ISOLATED',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
2021-12-13 14:14:30 +01:00
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/10323#issuecomment-992312847
|
|
|
|
|
// https://github.com/AdguardTeam/Scriptlets/issues/158
|
2022-09-17 18:46:42 +02:00
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/discussions/2270
|
2023-09-28 16:07:03 +02:00
|
|
|
|
function closeWindow(
|
2023-03-24 19:05:18 +01:00
|
|
|
|
arg1 = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof arg1 !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
2022-09-17 18:46:42 +02:00
|
|
|
|
let subject = '';
|
2023-03-26 15:13:17 +02:00
|
|
|
|
if ( /^\/.*\/$/.test(arg1) ) {
|
2022-09-17 18:46:42 +02:00
|
|
|
|
subject = window.location.href;
|
2023-03-26 15:13:17 +02:00
|
|
|
|
} else if ( arg1 !== '' ) {
|
2022-09-17 18:46:42 +02:00
|
|
|
|
subject = `${window.location.pathname}${window.location.search}`;
|
2021-12-13 14:14:30 +01:00
|
|
|
|
}
|
|
|
|
|
try {
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const re = safe.patternToRegex(arg1);
|
2022-09-17 18:46:42 +02:00
|
|
|
|
if ( re.test(subject) ) {
|
2021-12-13 14:14:30 +01:00
|
|
|
|
window.close();
|
|
|
|
|
}
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
console.log(ex);
|
|
|
|
|
}
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2021-12-13 14:14:30 +01:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2021-12-13 14:14:30 +01:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'window.name-defuser.js',
|
|
|
|
|
fn: windowNameDefuser,
|
|
|
|
|
});
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1228
|
|
|
|
|
function windowNameDefuser() {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
if ( window === window.top ) {
|
|
|
|
|
window.name = '';
|
|
|
|
|
}
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'overlay-buster.js',
|
|
|
|
|
fn: overlayBuster,
|
|
|
|
|
});
|
2019-07-06 18:36:28 +02:00
|
|
|
|
// Experimental: Generic nuisance overlay buster.
|
|
|
|
|
// if this works well and proves to be useful, this may end up
|
|
|
|
|
// as a stock tool in uBO's popup panel.
|
2023-03-24 19:05:18 +01:00
|
|
|
|
function overlayBuster() {
|
|
|
|
|
if ( window !== window.top ) { return; }
|
2019-07-06 18:36:28 +02:00
|
|
|
|
var tstart;
|
|
|
|
|
var ttl = 30000;
|
|
|
|
|
var delay = 0;
|
|
|
|
|
var delayStep = 50;
|
|
|
|
|
var buster = function() {
|
|
|
|
|
var docEl = document.documentElement,
|
|
|
|
|
bodyEl = document.body,
|
|
|
|
|
vw = Math.min(docEl.clientWidth, window.innerWidth),
|
|
|
|
|
vh = Math.min(docEl.clientHeight, window.innerHeight),
|
|
|
|
|
tol = Math.min(vw, vh) * 0.05,
|
|
|
|
|
el = document.elementFromPoint(vw/2, vh/2),
|
|
|
|
|
style, rect;
|
|
|
|
|
for (;;) {
|
|
|
|
|
if ( el === null || el.parentNode === null || el === bodyEl ) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
style = window.getComputedStyle(el);
|
|
|
|
|
if ( parseInt(style.zIndex, 10) >= 1000 || style.position === 'fixed' ) {
|
|
|
|
|
rect = el.getBoundingClientRect();
|
|
|
|
|
if ( rect.left <= tol && rect.top <= tol && (vw - rect.right) <= tol && (vh - rect.bottom) < tol ) {
|
|
|
|
|
el.parentNode.removeChild(el);
|
|
|
|
|
tstart = Date.now();
|
|
|
|
|
el = document.elementFromPoint(vw/2, vh/2);
|
|
|
|
|
bodyEl.style.setProperty('overflow', 'auto', 'important');
|
|
|
|
|
docEl.style.setProperty('overflow', 'auto', 'important');
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
el = el.parentNode;
|
|
|
|
|
}
|
|
|
|
|
if ( (Date.now() - tstart) < ttl ) {
|
|
|
|
|
delay = Math.min(delay + delayStep, 1000);
|
|
|
|
|
setTimeout(buster, delay);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var domReady = function(ev) {
|
|
|
|
|
if ( ev ) {
|
|
|
|
|
document.removeEventListener(ev.type, domReady);
|
|
|
|
|
}
|
|
|
|
|
tstart = Date.now();
|
|
|
|
|
setTimeout(buster, delay);
|
|
|
|
|
};
|
|
|
|
|
if ( document.readyState === 'loading' ) {
|
|
|
|
|
document.addEventListener('DOMContentLoaded', domReady);
|
|
|
|
|
} else {
|
|
|
|
|
domReady();
|
|
|
|
|
}
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'alert-buster.js',
|
|
|
|
|
fn: alertBuster,
|
|
|
|
|
});
|
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/8
|
|
|
|
|
function alertBuster() {
|
2022-05-08 17:22:32 +02:00
|
|
|
|
window.alert = new Proxy(window.alert, {
|
|
|
|
|
apply: function(a) {
|
|
|
|
|
console.info(a);
|
|
|
|
|
},
|
2023-05-20 23:18:44 +02:00
|
|
|
|
get(target, prop, receiver) {
|
|
|
|
|
if ( prop === 'toString' ) {
|
|
|
|
|
return target.toString.bind(target);
|
|
|
|
|
}
|
|
|
|
|
return Reflect.get(target, prop, receiver);
|
|
|
|
|
},
|
2022-05-08 17:22:32 +02:00
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'nowebrtc.js',
|
|
|
|
|
fn: noWebrtc,
|
|
|
|
|
});
|
|
|
|
|
// Prevent web pages from using RTCPeerConnection(), and report attempts in console.
|
|
|
|
|
function noWebrtc() {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
var rtcName = window.RTCPeerConnection ? 'RTCPeerConnection' : (
|
|
|
|
|
window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : ''
|
|
|
|
|
);
|
|
|
|
|
if ( rtcName === '' ) { return; }
|
|
|
|
|
var log = console.log.bind(console);
|
|
|
|
|
var pc = function(cfg) {
|
|
|
|
|
log('Document tried to create an RTCPeerConnection: %o', cfg);
|
|
|
|
|
};
|
|
|
|
|
const noop = function() {
|
|
|
|
|
};
|
|
|
|
|
pc.prototype = {
|
|
|
|
|
close: noop,
|
|
|
|
|
createDataChannel: noop,
|
|
|
|
|
createOffer: noop,
|
|
|
|
|
setRemoteDescription: noop,
|
|
|
|
|
toString: function() {
|
|
|
|
|
return '[object RTCPeerConnection]';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var z = window[rtcName];
|
|
|
|
|
window[rtcName] = pc.bind(window);
|
|
|
|
|
if ( z.prototype ) {
|
|
|
|
|
z.prototype.createDataChannel = function() {
|
|
|
|
|
return {
|
|
|
|
|
close: function() {},
|
|
|
|
|
send: function() {}
|
|
|
|
|
};
|
|
|
|
|
}.bind(null);
|
|
|
|
|
}
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'disable-newtab-links.js',
|
|
|
|
|
fn: disableNewtabLinks,
|
|
|
|
|
});
|
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/913
|
|
|
|
|
function disableNewtabLinks() {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
document.addEventListener('click', function(ev) {
|
|
|
|
|
var target = ev.target;
|
|
|
|
|
while ( target !== null ) {
|
|
|
|
|
if ( target.localName === 'a' && target.hasAttribute('target') ) {
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
target = target.parentNode;
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
2023-11-12 16:39:43 +01:00
|
|
|
|
name: 'remove-cookie.js',
|
2023-07-12 00:25:21 +02:00
|
|
|
|
aliases: [
|
2023-11-12 16:39:43 +01:00
|
|
|
|
'cookie-remover.js',
|
2023-07-12 00:25:21 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
fn: cookieRemover,
|
2023-06-17 17:53:08 +02:00
|
|
|
|
world: 'ISOLATED',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
// https://github.com/NanoAdblocker/NanoFilters/issues/149
|
|
|
|
|
function cookieRemover(
|
|
|
|
|
needle = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof needle !== 'string' ) { return; }
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const reName = safe.patternToRegex(needle);
|
2023-11-12 16:35:28 +01:00
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 1);
|
2023-11-12 17:44:24 +01:00
|
|
|
|
const throttle = (fn, ms = 500) => {
|
2023-11-12 16:35:28 +01:00
|
|
|
|
if ( throttle.timer !== undefined ) { return; }
|
|
|
|
|
throttle.timer = setTimeout(( ) => {
|
|
|
|
|
throttle.timer = undefined;
|
|
|
|
|
fn();
|
|
|
|
|
}, ms);
|
|
|
|
|
};
|
|
|
|
|
const removeCookie = ( ) => {
|
2019-07-06 18:36:28 +02:00
|
|
|
|
document.cookie.split(';').forEach(cookieStr => {
|
2023-11-12 16:35:28 +01:00
|
|
|
|
const pos = cookieStr.indexOf('=');
|
2019-07-06 18:36:28 +02:00
|
|
|
|
if ( pos === -1 ) { return; }
|
2023-11-12 16:35:28 +01:00
|
|
|
|
const cookieName = cookieStr.slice(0, pos).trim();
|
|
|
|
|
if ( reName.test(cookieName) === false ) { return; }
|
|
|
|
|
const part1 = cookieName + '=';
|
|
|
|
|
const part2a = '; domain=' + document.location.hostname;
|
|
|
|
|
const part2b = '; domain=.' + document.location.hostname;
|
2020-06-24 23:18:14 +02:00
|
|
|
|
let part2c, part2d;
|
2023-11-12 16:35:28 +01:00
|
|
|
|
const domain = document.domain;
|
2020-06-24 23:18:14 +02:00
|
|
|
|
if ( domain ) {
|
|
|
|
|
if ( domain !== document.location.hostname ) {
|
|
|
|
|
part2c = '; domain=.' + domain;
|
|
|
|
|
}
|
|
|
|
|
if ( domain.startsWith('www.') ) {
|
|
|
|
|
part2d = '; domain=' + domain.replace('www', '');
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-12 16:35:28 +01:00
|
|
|
|
const part3 = '; path=/';
|
|
|
|
|
const part4 = '; Max-Age=-1000; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
2019-07-06 18:36:28 +02:00
|
|
|
|
document.cookie = part1 + part4;
|
|
|
|
|
document.cookie = part1 + part2a + part4;
|
|
|
|
|
document.cookie = part1 + part2b + part4;
|
|
|
|
|
document.cookie = part1 + part3 + part4;
|
|
|
|
|
document.cookie = part1 + part2a + part3 + part4;
|
|
|
|
|
document.cookie = part1 + part2b + part3 + part4;
|
|
|
|
|
if ( part2c !== undefined ) {
|
|
|
|
|
document.cookie = part1 + part2c + part3 + part4;
|
|
|
|
|
}
|
2020-06-24 23:18:14 +02:00
|
|
|
|
if ( part2d !== undefined ) {
|
|
|
|
|
document.cookie = part1 + part2d + part3 + part4;
|
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
});
|
|
|
|
|
};
|
2023-11-12 17:44:24 +01:00
|
|
|
|
removeCookie();
|
|
|
|
|
window.addEventListener('beforeunload', removeCookie);
|
|
|
|
|
if ( typeof extraArgs.when !== 'string' ) { return; }
|
|
|
|
|
const supportedEventTypes = [ 'scroll', 'keydown' ];
|
|
|
|
|
const eventTypes = extraArgs.when.split(/\s/);
|
|
|
|
|
for ( const type of eventTypes ) {
|
|
|
|
|
if ( supportedEventTypes.includes(type) === false ) { continue; }
|
|
|
|
|
document.addEventListener(type, ( ) => {
|
2023-11-12 16:35:28 +01:00
|
|
|
|
throttle(removeCookie);
|
|
|
|
|
}, { passive: true });
|
|
|
|
|
}
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2019-07-08 14:56:36 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-08 14:56:36 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'xml-prune.js',
|
|
|
|
|
fn: xmlPrune,
|
2023-03-26 15:13:17 +02:00
|
|
|
|
dependencies: [
|
2023-05-27 23:26:19 +02:00
|
|
|
|
'safe-self.fn',
|
2023-03-26 15:13:17 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function xmlPrune(
|
|
|
|
|
selector = '',
|
|
|
|
|
selectorCheck = '',
|
|
|
|
|
urlPattern = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof selector !== 'string' ) { return; }
|
2022-09-25 12:57:51 +02:00
|
|
|
|
if ( selector === '' ) { return; }
|
2023-07-27 14:39:28 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('xml-prune', selector, selectorCheck, urlPattern);
|
2023-08-08 13:41:21 +02:00
|
|
|
|
const reUrl = safe.patternToRegex(urlPattern);
|
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
2023-05-27 23:26:19 +02:00
|
|
|
|
const queryAll = (xmlDoc, selector) => {
|
|
|
|
|
const isXpath = /^xpath\(.+\)$/.test(selector);
|
|
|
|
|
if ( isXpath === false ) {
|
|
|
|
|
return Array.from(xmlDoc.querySelectorAll(selector));
|
|
|
|
|
}
|
|
|
|
|
const xpr = xmlDoc.evaluate(
|
|
|
|
|
selector.slice(6, -1),
|
|
|
|
|
xmlDoc,
|
|
|
|
|
null,
|
|
|
|
|
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
|
|
|
|
|
null
|
|
|
|
|
);
|
|
|
|
|
const out = [];
|
|
|
|
|
for ( let i = 0; i < xpr.snapshotLength; i++ ) {
|
|
|
|
|
const node = xpr.snapshotItem(i);
|
|
|
|
|
out.push(node);
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
};
|
2023-05-27 15:14:19 +02:00
|
|
|
|
const pruneFromDoc = xmlDoc => {
|
2022-09-25 02:49:00 +02:00
|
|
|
|
try {
|
|
|
|
|
if ( selectorCheck !== '' && xmlDoc.querySelector(selectorCheck) === null ) {
|
2023-05-27 15:14:19 +02:00
|
|
|
|
return xmlDoc;
|
2022-09-25 02:49:00 +02:00
|
|
|
|
}
|
2023-07-27 14:39:28 +02:00
|
|
|
|
if ( extraArgs.logdoc ) {
|
|
|
|
|
const serializer = new XMLSerializer();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Document is\n\t${serializer.serializeToString(xmlDoc)}`);
|
2023-07-27 14:39:28 +02:00
|
|
|
|
}
|
2023-06-05 14:51:20 +02:00
|
|
|
|
const items = queryAll(xmlDoc, selector);
|
2023-07-27 14:39:28 +02:00
|
|
|
|
if ( items.length === 0 ) { return xmlDoc; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Removing ${items.length} items`);
|
2023-07-27 14:39:28 +02:00
|
|
|
|
for ( const item of items ) {
|
|
|
|
|
if ( item.nodeType === 1 ) {
|
|
|
|
|
item.remove();
|
|
|
|
|
} else if ( item.nodeType === 2 ) {
|
|
|
|
|
item.ownerElement.removeAttribute(item.nodeName);
|
2022-09-25 02:49:00 +02:00
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, `${item.constructor.name}.${item.nodeName} removed`);
|
2022-09-25 02:49:00 +02:00
|
|
|
|
}
|
|
|
|
|
} catch(ex) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboErr(logPrefix, `Error: ${ex}`);
|
2022-09-25 02:49:00 +02:00
|
|
|
|
}
|
2023-05-27 15:14:19 +02:00
|
|
|
|
return xmlDoc;
|
|
|
|
|
};
|
|
|
|
|
const pruneFromText = text => {
|
|
|
|
|
if ( (/^\s*</.test(text) && />\s*$/.test(text)) === false ) {
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const xmlParser = new DOMParser();
|
|
|
|
|
const xmlDoc = xmlParser.parseFromString(text, 'text/xml');
|
|
|
|
|
pruneFromDoc(xmlDoc);
|
|
|
|
|
const serializer = new XMLSerializer();
|
|
|
|
|
text = serializer.serializeToString(xmlDoc);
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
}
|
2022-09-25 02:49:00 +02:00
|
|
|
|
return text;
|
|
|
|
|
};
|
2022-09-25 12:49:41 +02:00
|
|
|
|
const urlFromArg = arg => {
|
|
|
|
|
if ( typeof arg === 'string' ) { return arg; }
|
|
|
|
|
if ( arg instanceof Request ) { return arg.url; }
|
|
|
|
|
return String(arg);
|
|
|
|
|
};
|
2022-09-25 02:49:00 +02:00
|
|
|
|
self.fetch = new Proxy(self.fetch, {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
2023-08-20 14:36:16 +02:00
|
|
|
|
const fetchPromise = Reflect.apply(target, thisArg, args);
|
2022-09-25 12:57:51 +02:00
|
|
|
|
if ( reUrl.test(urlFromArg(args[0])) === false ) {
|
2023-08-20 14:36:16 +02:00
|
|
|
|
return fetchPromise;
|
|
|
|
|
}
|
|
|
|
|
return fetchPromise.then(responseBefore => {
|
|
|
|
|
const response = responseBefore.clone();
|
|
|
|
|
return response.text().then(text => {
|
|
|
|
|
const responseAfter = new Response(pruneFromText(text), {
|
|
|
|
|
status: responseBefore.status,
|
|
|
|
|
statusText: responseBefore.statusText,
|
|
|
|
|
headers: responseBefore.headers,
|
|
|
|
|
});
|
|
|
|
|
Object.defineProperties(responseAfter, {
|
|
|
|
|
ok: { value: responseBefore.ok },
|
|
|
|
|
redirected: { value: responseBefore.redirected },
|
|
|
|
|
type: { value: responseBefore.type },
|
|
|
|
|
url: { value: responseBefore.url },
|
|
|
|
|
});
|
|
|
|
|
return responseAfter;
|
|
|
|
|
}).catch(( ) =>
|
|
|
|
|
responseBefore
|
|
|
|
|
);
|
|
|
|
|
});
|
2022-09-25 02:49:00 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
2023-05-27 15:14:19 +02:00
|
|
|
|
self.XMLHttpRequest.prototype.open = new Proxy(self.XMLHttpRequest.prototype.open, {
|
|
|
|
|
apply: async (target, thisArg, args) => {
|
|
|
|
|
if ( reUrl.test(urlFromArg(args[1])) === false ) {
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
|
|
|
|
thisArg.addEventListener('readystatechange', function() {
|
|
|
|
|
if ( thisArg.readyState !== 4 ) { return; }
|
|
|
|
|
const type = thisArg.responseType;
|
2023-07-27 14:39:28 +02:00
|
|
|
|
if (
|
|
|
|
|
type === 'document' ||
|
|
|
|
|
type === '' && thisArg.responseXML instanceof XMLDocument
|
|
|
|
|
) {
|
2023-07-25 15:05:39 +02:00
|
|
|
|
pruneFromDoc(thisArg.responseXML);
|
2023-12-22 16:15:37 +01:00
|
|
|
|
const serializer = new XMLSerializer();
|
|
|
|
|
const textout = serializer.serializeToString(thisArg.responseXML);
|
|
|
|
|
Object.defineProperty(thisArg, 'responseText', { value: textout });
|
2023-07-25 15:05:39 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-07-27 14:39:28 +02:00
|
|
|
|
if (
|
|
|
|
|
type === 'text' ||
|
|
|
|
|
type === '' && typeof thisArg.responseText === 'string'
|
|
|
|
|
) {
|
2023-05-27 15:14:19 +02:00
|
|
|
|
const textin = thisArg.responseText;
|
|
|
|
|
const textout = pruneFromText(textin);
|
|
|
|
|
if ( textout === textin ) { return; }
|
|
|
|
|
Object.defineProperty(thisArg, 'response', { value: textout });
|
|
|
|
|
Object.defineProperty(thisArg, 'responseText', { value: textout });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2022-09-25 02:49:00 +02:00
|
|
|
|
|
2023-03-26 15:13:17 +02:00
|
|
|
|
/******************************************************************************/
|
2019-07-08 14:56:36 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'm3u-prune.js',
|
|
|
|
|
fn: m3uPrune,
|
2023-06-18 20:29:11 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
2022-09-27 04:27:50 +02:00
|
|
|
|
// https://en.wikipedia.org/wiki/M3U
|
2023-03-24 19:05:18 +01:00
|
|
|
|
function m3uPrune(
|
|
|
|
|
m3uPattern = '',
|
|
|
|
|
urlPattern = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof m3uPattern !== 'string' ) { return; }
|
2023-06-18 20:29:11 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('m3u-prune', m3uPattern, urlPattern);
|
|
|
|
|
const toLog = [];
|
2022-09-27 04:27:50 +02:00
|
|
|
|
const regexFromArg = arg => {
|
|
|
|
|
if ( arg === '' ) { return /^/; }
|
2023-03-12 22:45:02 +01:00
|
|
|
|
const match = /^\/(.+)\/([gms]*)$/.exec(arg);
|
|
|
|
|
if ( match !== null ) {
|
|
|
|
|
let flags = match[2] || '';
|
|
|
|
|
if ( flags.includes('m') ) { flags += 's'; }
|
|
|
|
|
return new RegExp(match[1], flags);
|
|
|
|
|
}
|
2022-10-24 18:37:04 +02:00
|
|
|
|
return new RegExp(
|
|
|
|
|
arg.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*+/g, '.*?')
|
|
|
|
|
);
|
2022-09-27 04:27:50 +02:00
|
|
|
|
};
|
|
|
|
|
const reM3u = regexFromArg(m3uPattern);
|
|
|
|
|
const reUrl = regexFromArg(urlPattern);
|
2022-09-27 04:37:11 +02:00
|
|
|
|
const pruneSpliceoutBlock = (lines, i) => {
|
|
|
|
|
if ( lines[i].startsWith('#EXT-X-CUE:TYPE="SpliceOut"') === false ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
toLog.push(`\t${lines[i]}`);
|
2022-09-27 04:37:11 +02:00
|
|
|
|
lines[i] = undefined; i += 1;
|
|
|
|
|
if ( lines[i].startsWith('#EXT-X-ASSET:CAID') ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
toLog.push(`\t${lines[i]}`);
|
2022-09-27 04:37:11 +02:00
|
|
|
|
lines[i] = undefined; i += 1;
|
|
|
|
|
}
|
|
|
|
|
if ( lines[i].startsWith('#EXT-X-SCTE35:') ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
toLog.push(`\t${lines[i]}`);
|
2022-09-27 04:37:11 +02:00
|
|
|
|
lines[i] = undefined; i += 1;
|
|
|
|
|
}
|
|
|
|
|
if ( lines[i].startsWith('#EXT-X-CUE-IN') ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
toLog.push(`\t${lines[i]}`);
|
2022-09-27 04:37:11 +02:00
|
|
|
|
lines[i] = undefined; i += 1;
|
|
|
|
|
}
|
|
|
|
|
if ( lines[i].startsWith('#EXT-X-SCTE35:') ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
toLog.push(`\t${lines[i]}`);
|
2022-09-27 04:37:11 +02:00
|
|
|
|
lines[i] = undefined; i += 1;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
const pruneInfBlock = (lines, i) => {
|
|
|
|
|
if ( lines[i].startsWith('#EXTINF') === false ) { return false; }
|
|
|
|
|
if ( reM3u.test(lines[i+1]) === false ) { return false; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
toLog.push('Discarding', `\t${lines[i]}, \t${lines[i+1]}`);
|
2022-09-27 04:37:11 +02:00
|
|
|
|
lines[i] = lines[i+1] = undefined; i += 2;
|
|
|
|
|
if ( lines[i].startsWith('#EXT-X-DISCONTINUITY') ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
toLog.push(`\t${lines[i]}`);
|
2022-09-27 04:37:11 +02:00
|
|
|
|
lines[i] = undefined; i += 1;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
2022-09-27 04:27:50 +02:00
|
|
|
|
const pruner = text => {
|
|
|
|
|
if ( (/^\s*#EXTM3U/.test(text)) === false ) { return text; }
|
2024-04-11 21:44:07 +02:00
|
|
|
|
if ( m3uPattern === '' ) {
|
|
|
|
|
safe.uboLog(` Content:\n${text}`);
|
|
|
|
|
return text;
|
|
|
|
|
}
|
2023-03-12 22:45:02 +01:00
|
|
|
|
if ( reM3u.multiline ) {
|
|
|
|
|
reM3u.lastIndex = 0;
|
|
|
|
|
for (;;) {
|
|
|
|
|
const match = reM3u.exec(text);
|
|
|
|
|
if ( match === null ) { break; }
|
2023-06-18 20:29:11 +02:00
|
|
|
|
let discard = match[0];
|
|
|
|
|
let before = text.slice(0, match.index);
|
|
|
|
|
if (
|
|
|
|
|
/^[\n\r]+/.test(discard) === false &&
|
|
|
|
|
/[\n\r]+$/.test(before) === false
|
|
|
|
|
) {
|
|
|
|
|
const startOfLine = /[^\n\r]+$/.exec(before);
|
|
|
|
|
if ( startOfLine !== null ) {
|
|
|
|
|
before = before.slice(0, startOfLine.index);
|
|
|
|
|
discard = startOfLine[0] + discard;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let after = text.slice(match.index + match[0].length);
|
|
|
|
|
if (
|
|
|
|
|
/[\n\r]+$/.test(discard) === false &&
|
|
|
|
|
/^[\n\r]+/.test(after) === false
|
|
|
|
|
) {
|
|
|
|
|
const endOfLine = /^[^\n\r]+/.exec(after);
|
|
|
|
|
if ( endOfLine !== null ) {
|
|
|
|
|
after = after.slice(endOfLine.index);
|
|
|
|
|
discard += discard + endOfLine[0];
|
2023-03-12 22:45:02 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-18 20:29:11 +02:00
|
|
|
|
text = before.trim() + '\n' + after.trim();
|
|
|
|
|
reM3u.lastIndex = before.length + 1;
|
2024-01-25 18:20:38 +01:00
|
|
|
|
toLog.push('Discarding', ...discard.split(/\n+/).map(s => `\t${s}`));
|
2023-03-12 22:45:02 +01:00
|
|
|
|
if ( reM3u.global === false ) { break; }
|
|
|
|
|
}
|
2023-06-18 20:29:11 +02:00
|
|
|
|
return text;
|
2023-03-12 22:45:02 +01:00
|
|
|
|
}
|
2022-09-27 04:37:11 +02:00
|
|
|
|
const lines = text.split(/\n\r|\n|\r/);
|
2022-09-27 04:27:50 +02:00
|
|
|
|
for ( let i = 0; i < lines.length; i++ ) {
|
2022-09-27 04:37:11 +02:00
|
|
|
|
if ( lines[i] === undefined ) { continue; }
|
|
|
|
|
if ( pruneSpliceoutBlock(lines, i) ) { continue; }
|
|
|
|
|
if ( pruneInfBlock(lines, i) ) { continue; }
|
|
|
|
|
}
|
|
|
|
|
return lines.filter(l => l !== undefined).join('\n');
|
2022-09-27 04:27:50 +02:00
|
|
|
|
};
|
|
|
|
|
const urlFromArg = arg => {
|
|
|
|
|
if ( typeof arg === 'string' ) { return arg; }
|
|
|
|
|
if ( arg instanceof Request ) { return arg.url; }
|
|
|
|
|
return String(arg);
|
|
|
|
|
};
|
|
|
|
|
const realFetch = self.fetch;
|
|
|
|
|
self.fetch = new Proxy(self.fetch, {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
|
if ( reUrl.test(urlFromArg(args[0])) === false ) {
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
|
|
|
|
return realFetch(...args).then(realResponse =>
|
2024-01-25 18:20:38 +01:00
|
|
|
|
realResponse.text().then(text => {
|
|
|
|
|
const response = new Response(pruner(text), {
|
2022-09-27 04:27:50 +02:00
|
|
|
|
status: realResponse.status,
|
|
|
|
|
statusText: realResponse.statusText,
|
|
|
|
|
headers: realResponse.headers,
|
2024-01-25 18:20:38 +01:00
|
|
|
|
});
|
|
|
|
|
if ( toLog.length !== 0 ) {
|
|
|
|
|
toLog.unshift(logPrefix);
|
|
|
|
|
safe.uboLog(toLog.join('\n'));
|
|
|
|
|
}
|
|
|
|
|
return response;
|
|
|
|
|
})
|
2022-09-27 04:27:50 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
self.XMLHttpRequest.prototype.open = new Proxy(self.XMLHttpRequest.prototype.open, {
|
|
|
|
|
apply: async (target, thisArg, args) => {
|
|
|
|
|
if ( reUrl.test(urlFromArg(args[1])) === false ) {
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
|
|
|
|
thisArg.addEventListener('readystatechange', function() {
|
|
|
|
|
if ( thisArg.readyState !== 4 ) { return; }
|
|
|
|
|
const type = thisArg.responseType;
|
|
|
|
|
if ( type !== '' && type !== 'text' ) { return; }
|
|
|
|
|
const textin = thisArg.responseText;
|
|
|
|
|
const textout = pruner(textin);
|
|
|
|
|
if ( textout === textin ) { return; }
|
2022-09-27 04:37:11 +02:00
|
|
|
|
Object.defineProperty(thisArg, 'response', { value: textout });
|
|
|
|
|
Object.defineProperty(thisArg, 'responseText', { value: textout });
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( toLog.length !== 0 ) {
|
|
|
|
|
toLog.unshift(logPrefix);
|
|
|
|
|
safe.uboLog(toLog.join('\n'));
|
|
|
|
|
}
|
2022-09-27 04:27:50 +02:00
|
|
|
|
});
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2022-09-27 04:27:50 +02:00
|
|
|
|
|
2023-07-06 14:51:31 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* @scriptlet href-sanitizer
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Set the `href` attribute to a value found in the DOM at, or below the
|
|
|
|
|
* targeted `a` element.
|
|
|
|
|
*
|
|
|
|
|
* ### Syntax
|
|
|
|
|
*
|
|
|
|
|
* ```text
|
|
|
|
|
* example.org##+js(href-sanitizer, selector [, source])
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* - `selector`: required, CSS selector, specifies `a` elements for which the
|
2023-07-14 20:19:30 +02:00
|
|
|
|
* `href` attribute must be overridden.
|
2023-07-06 14:51:31 +02:00
|
|
|
|
* - `source`: optional, default to `text`, specifies from where to get the
|
|
|
|
|
* value which will override the `href` attribute.
|
|
|
|
|
* - `text`: the value will be the first valid URL found in the text
|
|
|
|
|
* content of the targeted `a` element.
|
|
|
|
|
* - `[attr]`: the value will be the attribute _attr_ of the targeted `a`
|
|
|
|
|
* element.
|
|
|
|
|
* - `?param`: the value will be the query parameter _param_ of the URL
|
|
|
|
|
* found in the `href` attribute of the targeted `a` element.
|
|
|
|
|
*
|
|
|
|
|
* ### Examples
|
|
|
|
|
*
|
|
|
|
|
* example.org##+js(href-sanitizer, a)
|
|
|
|
|
* example.org##+js(href-sanitizer, a[title], [title])
|
|
|
|
|
* example.org##+js(href-sanitizer, a[href*="/away.php?to="], ?to)
|
|
|
|
|
*
|
|
|
|
|
* */
|
2022-09-27 04:27:50 +02:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'href-sanitizer.js',
|
|
|
|
|
fn: hrefSanitizer,
|
2023-07-06 14:51:31 +02:00
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
|
|
|
|
'run-at.fn',
|
2024-01-25 18:20:38 +01:00
|
|
|
|
'safe-self.fn',
|
2023-07-06 14:51:31 +02:00
|
|
|
|
],
|
2023-03-24 19:05:18 +01:00
|
|
|
|
});
|
|
|
|
|
function hrefSanitizer(
|
|
|
|
|
selector = '',
|
|
|
|
|
source = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof selector !== 'string' ) { return; }
|
2023-03-09 14:49:26 +01:00
|
|
|
|
if ( selector === '' ) { return; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('href-sanitizer', selector, source);
|
2023-03-09 19:37:06 +01:00
|
|
|
|
if ( source === '' ) { source = 'text'; }
|
2023-03-09 14:49:26 +01:00
|
|
|
|
const sanitizeCopycats = (href, text) => {
|
|
|
|
|
let elems = [];
|
|
|
|
|
try {
|
|
|
|
|
elems = document.querySelectorAll(`a[href="${href}"`);
|
|
|
|
|
}
|
|
|
|
|
catch(ex) {
|
|
|
|
|
}
|
|
|
|
|
for ( const elem of elems ) {
|
|
|
|
|
elem.setAttribute('href', text);
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
return elems.length;
|
2023-03-09 14:49:26 +01:00
|
|
|
|
};
|
2023-05-30 15:13:46 +02:00
|
|
|
|
const validateURL = text => {
|
|
|
|
|
if ( text === '' ) { return ''; }
|
|
|
|
|
if ( /[^\x21-\x7e]/.test(text) ) { return ''; }
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(text, document.location);
|
|
|
|
|
return url.href;
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
};
|
2023-03-09 19:37:06 +01:00
|
|
|
|
const extractText = (elem, source) => {
|
|
|
|
|
if ( /^\[.*\]$/.test(source) ) {
|
2023-05-30 15:13:46 +02:00
|
|
|
|
return elem.getAttribute(source.slice(1,-1).trim()) || '';
|
|
|
|
|
}
|
|
|
|
|
if ( source.startsWith('?') ) {
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(elem.href, document.location);
|
|
|
|
|
return url.searchParams.get(source.slice(1)) || '';
|
|
|
|
|
} catch(x) {
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
if ( source === 'text' ) {
|
|
|
|
|
return elem.textContent
|
|
|
|
|
.replace(/^[^\x21-\x7e]+/, '') // remove leading invalid characters
|
|
|
|
|
.replace(/[^\x21-\x7e]+$/, '') // remove trailing invalid characters
|
2024-03-20 14:31:17 +01:00
|
|
|
|
;
|
2023-05-30 15:13:46 +02:00
|
|
|
|
}
|
|
|
|
|
return '';
|
2023-03-09 19:37:06 +01:00
|
|
|
|
};
|
2023-03-09 14:49:26 +01:00
|
|
|
|
const sanitize = ( ) => {
|
|
|
|
|
let elems = [];
|
|
|
|
|
try {
|
|
|
|
|
elems = document.querySelectorAll(selector);
|
|
|
|
|
}
|
|
|
|
|
catch(ex) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for ( const elem of elems ) {
|
|
|
|
|
if ( elem.localName !== 'a' ) { continue; }
|
|
|
|
|
if ( elem.hasAttribute('href') === false ) { continue; }
|
|
|
|
|
const href = elem.getAttribute('href');
|
2023-03-09 19:37:06 +01:00
|
|
|
|
const text = extractText(elem, source);
|
2023-05-30 15:13:46 +02:00
|
|
|
|
const hrefAfter = validateURL(text);
|
|
|
|
|
if ( hrefAfter === '' ) { continue; }
|
|
|
|
|
if ( hrefAfter === href ) { continue; }
|
|
|
|
|
elem.setAttribute('href', hrefAfter);
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const count = sanitizeCopycats(href, hrefAfter);
|
|
|
|
|
safe.uboLog(logPrefix, `Sanitized ${count+1} links to\n${hrefAfter}`);
|
2023-03-09 14:49:26 +01:00
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
let observer, timer;
|
|
|
|
|
const onDomChanged = mutations => {
|
|
|
|
|
if ( timer !== undefined ) { return; }
|
|
|
|
|
let shouldSanitize = false;
|
|
|
|
|
for ( const mutation of mutations ) {
|
|
|
|
|
if ( mutation.addedNodes.length === 0 ) { continue; }
|
|
|
|
|
for ( const node of mutation.addedNodes ) {
|
|
|
|
|
if ( node.nodeType !== 1 ) { continue; }
|
|
|
|
|
shouldSanitize = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if ( shouldSanitize ) { break; }
|
|
|
|
|
}
|
|
|
|
|
if ( shouldSanitize === false ) { return; }
|
2024-02-17 18:53:57 +01:00
|
|
|
|
timer = self.requestIdleCallback(( ) => {
|
2023-03-09 14:49:26 +01:00
|
|
|
|
timer = undefined;
|
|
|
|
|
sanitize();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
const start = ( ) => {
|
|
|
|
|
if ( sanitize() === false ) { return; }
|
|
|
|
|
observer = new MutationObserver(onDomChanged);
|
|
|
|
|
observer.observe(document.body, {
|
|
|
|
|
subtree: true,
|
|
|
|
|
childList: true,
|
|
|
|
|
});
|
|
|
|
|
};
|
2023-07-06 14:51:31 +02:00
|
|
|
|
runAt(( ) => { start(); }, 'interactive');
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2023-03-09 14:49:26 +01:00
|
|
|
|
|
2023-07-06 14:51:31 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* @scriptlet call-nothrow
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Prevent a function call from throwing. The function will be called, however
|
|
|
|
|
* should it throw, the scriptlet will silently process the exception and
|
|
|
|
|
* returns as if no exception has occurred.
|
|
|
|
|
*
|
|
|
|
|
* ### Syntax
|
|
|
|
|
*
|
|
|
|
|
* ```text
|
|
|
|
|
* example.org##+js(call-nothrow, propertyChain)
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* - `propertyChain`: a chain of dot-separated properties which leads to the
|
|
|
|
|
* function to be trapped.
|
|
|
|
|
*
|
|
|
|
|
* ### Examples
|
|
|
|
|
*
|
|
|
|
|
* example.org##+js(call-nothrow, Object.defineProperty)
|
|
|
|
|
*
|
|
|
|
|
* */
|
2023-03-09 14:49:26 +01:00
|
|
|
|
|
2023-03-24 19:05:18 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'call-nothrow.js',
|
|
|
|
|
fn: callNothrow,
|
|
|
|
|
});
|
|
|
|
|
function callNothrow(
|
|
|
|
|
chain = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof chain !== 'string' ) { return; }
|
|
|
|
|
if ( chain === '' ) { return; }
|
2023-03-14 23:50:01 +01:00
|
|
|
|
const parts = chain.split('.');
|
|
|
|
|
let owner = window, prop;
|
|
|
|
|
for (;;) {
|
|
|
|
|
prop = parts.shift();
|
|
|
|
|
if ( parts.length === 0 ) { break; }
|
|
|
|
|
owner = owner[prop];
|
|
|
|
|
if ( owner instanceof Object === false ) { return; }
|
|
|
|
|
}
|
|
|
|
|
if ( prop === '' ) { return; }
|
|
|
|
|
const fn = owner[prop];
|
|
|
|
|
if ( typeof fn !== 'function' ) { return; }
|
|
|
|
|
owner[prop] = new Proxy(fn, {
|
|
|
|
|
apply: function(...args) {
|
|
|
|
|
let r;
|
|
|
|
|
try {
|
|
|
|
|
r = Reflect.apply(...args);
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
},
|
|
|
|
|
});
|
2023-03-24 19:05:18 +01:00
|
|
|
|
}
|
2023-03-14 23:50:01 +01:00
|
|
|
|
|
2023-05-20 23:18:44 +02:00
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'spoof-css.js',
|
|
|
|
|
fn: spoofCSS,
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
2023-05-20 23:18:44 +02:00
|
|
|
|
});
|
|
|
|
|
function spoofCSS(
|
|
|
|
|
selector,
|
|
|
|
|
...args
|
|
|
|
|
) {
|
|
|
|
|
if ( typeof selector !== 'string' ) { return; }
|
|
|
|
|
if ( selector === '' ) { return; }
|
|
|
|
|
const toCamelCase = s => s.replace(/-[a-z]/g, s => s.charAt(1).toUpperCase());
|
|
|
|
|
const propToValueMap = new Map();
|
|
|
|
|
for ( let i = 0; i < args.length; i += 2 ) {
|
|
|
|
|
if ( typeof args[i+0] !== 'string' ) { break; }
|
|
|
|
|
if ( args[i+0] === '' ) { break; }
|
|
|
|
|
if ( typeof args[i+1] !== 'string' ) { break; }
|
|
|
|
|
propToValueMap.set(toCamelCase(args[i+0]), args[i+1]);
|
|
|
|
|
}
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('spoof-css', selector, ...args);
|
|
|
|
|
const canDebug = scriptletGlobals.canDebug;
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
const shouldDebug = canDebug && propToValueMap.get('debug') || 0;
|
2024-03-20 14:31:17 +01:00
|
|
|
|
const instanceProperties = [ 'cssText', 'length', 'parentRule' ];
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
const spoofStyle = (prop, real) => {
|
|
|
|
|
const normalProp = toCamelCase(prop);
|
|
|
|
|
const shouldSpoof = propToValueMap.has(normalProp);
|
|
|
|
|
const value = shouldSpoof ? propToValueMap.get(normalProp) : real;
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( shouldSpoof ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Spoofing ${prop} to ${value}`);
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
};
|
2024-03-02 14:11:29 +01:00
|
|
|
|
const cloackFunc = (fn, thisArg, name) => {
|
|
|
|
|
const trap = fn.bind(thisArg);
|
|
|
|
|
Object.defineProperty(trap, 'name', { value: name });
|
2024-04-10 02:05:05 +02:00
|
|
|
|
Object.defineProperty(trap, 'toString', {
|
|
|
|
|
value: ( ) => `function ${name}() { [native code] }`
|
|
|
|
|
});
|
2024-03-02 14:11:29 +01:00
|
|
|
|
return trap;
|
|
|
|
|
};
|
2023-05-20 23:18:44 +02:00
|
|
|
|
self.getComputedStyle = new Proxy(self.getComputedStyle, {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
2024-03-20 14:31:17 +01:00
|
|
|
|
// eslint-disable-next-line no-debugger
|
|
|
|
|
if ( shouldDebug !== 0 ) { debugger; }
|
2023-05-20 23:18:44 +02:00
|
|
|
|
const style = Reflect.apply(target, thisArg, args);
|
|
|
|
|
const targetElements = new WeakSet(document.querySelectorAll(selector));
|
|
|
|
|
if ( targetElements.has(args[0]) === false ) { return style; }
|
|
|
|
|
const proxiedStyle = new Proxy(style, {
|
|
|
|
|
get(target, prop, receiver) {
|
2023-05-24 20:58:12 +02:00
|
|
|
|
if ( typeof target[prop] === 'function' ) {
|
2023-05-24 22:50:34 +02:00
|
|
|
|
if ( prop === 'getPropertyValue' ) {
|
2024-04-10 02:05:05 +02:00
|
|
|
|
return cloackFunc(function getPropertyValue(prop) {
|
2023-05-24 22:50:34 +02:00
|
|
|
|
return spoofStyle(prop, target[prop]);
|
2024-03-02 14:11:29 +01:00
|
|
|
|
}, target, 'getPropertyValue');
|
2023-05-24 22:50:34 +02:00
|
|
|
|
}
|
2024-03-02 14:11:29 +01:00
|
|
|
|
return cloackFunc(target[prop], target, prop);
|
2023-05-24 20:58:12 +02:00
|
|
|
|
}
|
2024-03-20 14:31:17 +01:00
|
|
|
|
if ( instanceProperties.includes(prop) ) {
|
|
|
|
|
return Reflect.get(target, prop);
|
|
|
|
|
}
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
return spoofStyle(prop, Reflect.get(target, prop, receiver));
|
2023-05-20 23:18:44 +02:00
|
|
|
|
},
|
2023-08-02 17:36:54 +02:00
|
|
|
|
getOwnPropertyDescriptor(target, prop) {
|
|
|
|
|
if ( propToValueMap.has(prop) ) {
|
|
|
|
|
return {
|
|
|
|
|
configurable: true,
|
|
|
|
|
enumerable: true,
|
|
|
|
|
value: propToValueMap.get(prop),
|
|
|
|
|
writable: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
|
|
|
},
|
2023-05-20 23:18:44 +02:00
|
|
|
|
});
|
|
|
|
|
return proxiedStyle;
|
|
|
|
|
},
|
|
|
|
|
get(target, prop, receiver) {
|
|
|
|
|
if ( prop === 'toString' ) {
|
|
|
|
|
return target.toString.bind(target);
|
|
|
|
|
}
|
|
|
|
|
return Reflect.get(target, prop, receiver);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
Element.prototype.getBoundingClientRect = new Proxy(Element.prototype.getBoundingClientRect, {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
2024-03-20 14:31:17 +01:00
|
|
|
|
// eslint-disable-next-line no-debugger
|
|
|
|
|
if ( shouldDebug !== 0 ) { debugger; }
|
2023-05-20 23:18:44 +02:00
|
|
|
|
const rect = Reflect.apply(target, thisArg, args);
|
|
|
|
|
const targetElements = new WeakSet(document.querySelectorAll(selector));
|
|
|
|
|
if ( targetElements.has(thisArg) === false ) { return rect; }
|
|
|
|
|
let { height, width } = rect;
|
|
|
|
|
if ( propToValueMap.has('width') ) {
|
|
|
|
|
width = parseFloat(propToValueMap.get('width'));
|
|
|
|
|
}
|
|
|
|
|
if ( propToValueMap.has('height') ) {
|
|
|
|
|
height = parseFloat(propToValueMap.get('height'));
|
|
|
|
|
}
|
|
|
|
|
return new self.DOMRect(rect.x, rect.y, width, height);
|
|
|
|
|
},
|
|
|
|
|
get(target, prop, receiver) {
|
|
|
|
|
if ( prop === 'toString' ) {
|
|
|
|
|
return target.toString.bind(target);
|
|
|
|
|
}
|
|
|
|
|
return Reflect.get(target, prop, receiver);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-25 14:51:26 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'remove-node-text.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'rmnt.js',
|
|
|
|
|
],
|
2023-05-25 14:51:26 +02:00
|
|
|
|
fn: removeNodeText,
|
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
2023-10-17 23:33:49 +02:00
|
|
|
|
'replace-node-text.fn',
|
2023-05-25 14:51:26 +02:00
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function removeNodeText(
|
|
|
|
|
nodeName,
|
|
|
|
|
condition,
|
|
|
|
|
...extraArgs
|
|
|
|
|
) {
|
2023-10-17 23:33:49 +02:00
|
|
|
|
replaceNodeTextFn(nodeName, '', '', 'condition', condition || '', ...extraArgs);
|
2023-05-25 14:51:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-16 01:57:10 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* set-cookie.js
|
|
|
|
|
*
|
|
|
|
|
* Set specified cookie to a specific value.
|
|
|
|
|
*
|
|
|
|
|
* Reference:
|
|
|
|
|
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-cookie.js
|
|
|
|
|
*
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'set-cookie.js',
|
|
|
|
|
fn: setCookie,
|
|
|
|
|
world: 'ISOLATED',
|
2023-06-16 17:32:12 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-10-23 00:19:18 +02:00
|
|
|
|
'set-cookie.fn',
|
2023-06-16 17:32:12 +02:00
|
|
|
|
],
|
2023-06-16 01:57:10 +02:00
|
|
|
|
});
|
|
|
|
|
function setCookie(
|
|
|
|
|
name = '',
|
|
|
|
|
value = '',
|
2023-06-16 17:32:12 +02:00
|
|
|
|
path = ''
|
2023-06-16 01:57:10 +02:00
|
|
|
|
) {
|
|
|
|
|
if ( name === '' ) { return; }
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
|
2023-06-16 17:32:12 +02:00
|
|
|
|
|
2023-07-20 13:53:14 +02:00
|
|
|
|
const validValues = [
|
2023-10-30 04:44:43 +01:00
|
|
|
|
'accept', 'reject',
|
|
|
|
|
'accepted', 'rejected', 'notaccepted',
|
|
|
|
|
'allow', 'deny',
|
|
|
|
|
'allowed', 'disallow',
|
|
|
|
|
'enable', 'disable',
|
|
|
|
|
'enabled', 'disabled',
|
2023-07-20 13:53:14 +02:00
|
|
|
|
'ok',
|
2023-10-21 02:43:52 +02:00
|
|
|
|
'on', 'off',
|
2023-11-05 22:07:58 +01:00
|
|
|
|
'true', 't', 'false', 'f',
|
2023-11-13 01:26:05 +01:00
|
|
|
|
'yes', 'y', 'no', 'n',
|
2023-11-12 18:51:58 +01:00
|
|
|
|
'necessary', 'required',
|
2023-07-20 13:53:14 +02:00
|
|
|
|
];
|
2023-11-13 01:26:05 +01:00
|
|
|
|
const normalized = value.toLowerCase();
|
|
|
|
|
const match = /^("?)(.+)\1$/.exec(normalized);
|
|
|
|
|
const unquoted = match && match[2] || normalized;
|
|
|
|
|
if ( validValues.includes(unquoted) === false ) {
|
|
|
|
|
if ( /^\d+$/.test(unquoted) === false ) { return; }
|
2023-06-16 01:57:10 +02:00
|
|
|
|
const n = parseInt(value, 10);
|
2024-03-15 17:29:22 +01:00
|
|
|
|
if ( n > 32767 ) { return; }
|
2023-06-16 01:57:10 +02:00
|
|
|
|
}
|
2023-06-16 17:32:12 +02:00
|
|
|
|
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const done = setCookieFn(
|
2023-10-23 00:19:18 +02:00
|
|
|
|
false,
|
2023-06-16 17:32:12 +02:00
|
|
|
|
name,
|
|
|
|
|
value,
|
|
|
|
|
'',
|
|
|
|
|
path,
|
2024-02-15 16:05:10 +01:00
|
|
|
|
safe.getExtraArgs(Array.from(arguments), 3)
|
2023-06-16 17:32:12 +02:00
|
|
|
|
);
|
2024-01-25 18:20:38 +01:00
|
|
|
|
|
|
|
|
|
if ( done ) {
|
|
|
|
|
safe.uboLog(logPrefix, 'Done');
|
|
|
|
|
}
|
2023-06-16 01:57:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-10-18 15:50:58 +02:00
|
|
|
|
// For compatibility with AdGuard
|
2023-10-18 15:48:08 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'set-cookie-reload.js',
|
|
|
|
|
fn: setCookieReload,
|
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
|
|
|
|
'set-cookie.js',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function setCookieReload(name, value, path, ...args) {
|
|
|
|
|
setCookie(name, value, path, 'reload', '1', ...args);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 16:58:10 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* set-local-storage-item.js
|
2023-07-04 13:13:22 +02:00
|
|
|
|
* set-session-storage-item.js
|
2023-06-20 16:58:10 +02:00
|
|
|
|
*
|
2023-07-04 13:13:22 +02:00
|
|
|
|
* Set a local/session storage entry to a specific, allowed value.
|
2023-06-20 16:58:10 +02:00
|
|
|
|
*
|
|
|
|
|
* Reference:
|
|
|
|
|
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-local-storage-item.js
|
2023-07-04 13:13:22 +02:00
|
|
|
|
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-session-storage-item.js
|
2023-06-20 16:58:10 +02:00
|
|
|
|
*
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'set-local-storage-item.js',
|
|
|
|
|
fn: setLocalStorageItem,
|
|
|
|
|
world: 'ISOLATED',
|
2023-07-04 13:13:22 +02:00
|
|
|
|
dependencies: [
|
2023-10-21 02:10:35 +02:00
|
|
|
|
'set-local-storage-item.fn',
|
2023-07-04 13:13:22 +02:00
|
|
|
|
],
|
2023-06-20 16:58:10 +02:00
|
|
|
|
});
|
2023-07-04 13:13:22 +02:00
|
|
|
|
function setLocalStorageItem(key = '', value = '') {
|
2023-10-21 02:10:35 +02:00
|
|
|
|
setLocalStorageItemFn('local', false, key, value);
|
2023-07-04 13:13:22 +02:00
|
|
|
|
}
|
2023-06-20 16:58:10 +02:00
|
|
|
|
|
2023-07-04 13:13:22 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'set-session-storage-item.js',
|
|
|
|
|
fn: setSessionStorageItem,
|
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
2023-10-21 02:10:35 +02:00
|
|
|
|
'set-local-storage-item.fn',
|
2023-07-04 13:13:22 +02:00
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function setSessionStorageItem(key = '', value = '') {
|
2023-10-21 02:10:35 +02:00
|
|
|
|
setLocalStorageItemFn('session', false, key, value);
|
2023-06-20 16:58:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-06 14:51:31 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* @scriptlet set-attr
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Sets the specified attribute on the specified elements. This scriptlet runs
|
|
|
|
|
* once when the page loads then afterward on DOM mutations.
|
|
|
|
|
|
|
|
|
|
* Reference: https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-attr.js
|
|
|
|
|
*
|
|
|
|
|
* ### Syntax
|
|
|
|
|
*
|
|
|
|
|
* ```text
|
|
|
|
|
* example.org##+js(set-attr, selector, attr [, value])
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* - `selector`: CSS selector of DOM elements for which the attribute `attr`
|
|
|
|
|
* must be modified.
|
|
|
|
|
* - `attr`: the name of the attribute to modify
|
|
|
|
|
* - `value`: the value to assign to the target attribute. Possible values:
|
|
|
|
|
* - `''`: empty string (default)
|
|
|
|
|
* - `true`
|
|
|
|
|
* - `false`
|
|
|
|
|
* - positive decimal integer 0 <= value < 32768
|
|
|
|
|
* - `[other]`: copy the value from attribute `other` on the same element
|
|
|
|
|
* */
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'set-attr.js',
|
|
|
|
|
fn: setAttr,
|
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
|
|
|
|
'run-at.fn',
|
2024-02-14 01:41:25 +01:00
|
|
|
|
'safe-self.fn',
|
2023-07-06 14:51:31 +02:00
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function setAttr(
|
|
|
|
|
selector = '',
|
|
|
|
|
attr = '',
|
|
|
|
|
value = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( selector === '' ) { return; }
|
2024-02-14 01:41:25 +01:00
|
|
|
|
if ( attr === '' ) { return; }
|
2023-07-06 14:51:31 +02:00
|
|
|
|
|
2024-02-14 01:41:25 +01:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('set-attr', attr, value);
|
2023-07-06 14:51:31 +02:00
|
|
|
|
const validValues = [ '', 'false', 'true' ];
|
|
|
|
|
let copyFrom = '';
|
|
|
|
|
|
2023-07-11 21:10:27 +02:00
|
|
|
|
if ( validValues.includes(value.toLowerCase()) === false ) {
|
2023-07-06 14:51:31 +02:00
|
|
|
|
if ( /^\d+$/.test(value) ) {
|
|
|
|
|
const n = parseInt(value, 10);
|
|
|
|
|
if ( n >= 32768 ) { return; }
|
|
|
|
|
value = `${n}`;
|
|
|
|
|
} else if ( /^\[.+\]$/.test(value) ) {
|
|
|
|
|
copyFrom = value.slice(1, -1);
|
|
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const extractValue = elem => {
|
|
|
|
|
if ( copyFrom !== '' ) {
|
|
|
|
|
return elem.getAttribute(copyFrom) || '';
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const applySetAttr = ( ) => {
|
|
|
|
|
const elems = [];
|
|
|
|
|
try {
|
|
|
|
|
elems.push(...document.querySelectorAll(selector));
|
|
|
|
|
}
|
|
|
|
|
catch(ex) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for ( const elem of elems ) {
|
|
|
|
|
const before = elem.getAttribute(attr);
|
|
|
|
|
const after = extractValue(elem);
|
|
|
|
|
if ( after === before ) { continue; }
|
2024-02-14 14:37:01 +01:00
|
|
|
|
if ( after !== '' && /^on/i.test(attr) ) {
|
|
|
|
|
if ( attr.toLowerCase() in elem ) { continue; }
|
|
|
|
|
}
|
2023-07-06 14:51:31 +02:00
|
|
|
|
elem.setAttribute(attr, after);
|
2024-02-14 01:41:25 +01:00
|
|
|
|
safe.uboLog(logPrefix, `${attr}="${after}"`);
|
2023-07-06 14:51:31 +02:00
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
let observer, timer;
|
|
|
|
|
const onDomChanged = mutations => {
|
|
|
|
|
if ( timer !== undefined ) { return; }
|
|
|
|
|
let shouldWork = false;
|
|
|
|
|
for ( const mutation of mutations ) {
|
|
|
|
|
if ( mutation.addedNodes.length === 0 ) { continue; }
|
|
|
|
|
for ( const node of mutation.addedNodes ) {
|
|
|
|
|
if ( node.nodeType !== 1 ) { continue; }
|
|
|
|
|
shouldWork = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if ( shouldWork ) { break; }
|
|
|
|
|
}
|
|
|
|
|
if ( shouldWork === false ) { return; }
|
|
|
|
|
timer = self.requestAnimationFrame(( ) => {
|
|
|
|
|
timer = undefined;
|
|
|
|
|
applySetAttr();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
const start = ( ) => {
|
|
|
|
|
if ( applySetAttr() === false ) { return; }
|
|
|
|
|
observer = new MutationObserver(onDomChanged);
|
|
|
|
|
observer.observe(document.body, {
|
|
|
|
|
subtree: true,
|
|
|
|
|
childList: true,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
runAt(( ) => { start(); }, 'idle');
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-28 17:26:45 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* @scriptlet prevent-canvas
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Prevent usage of specific or all (default) canvas APIs.
|
|
|
|
|
*
|
|
|
|
|
* ### Syntax
|
|
|
|
|
*
|
|
|
|
|
* ```text
|
|
|
|
|
* example.com##+js(prevent-canvas [, contextType])
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* - `contextType`: A specific type of canvas API to prevent (default to all
|
|
|
|
|
* APIs). Can be a string or regex which will be matched against the type
|
|
|
|
|
* used in getContext() call. Prepend with `!` to test for no-match.
|
|
|
|
|
*
|
|
|
|
|
* ### Examples
|
|
|
|
|
*
|
|
|
|
|
* 1. Prevent `example.com` from accessing all canvas APIs
|
|
|
|
|
*
|
|
|
|
|
* ```adblock
|
|
|
|
|
* example.com##+js(prevent-canvas)
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* 2. Prevent access to any flavor of WebGL API, everywhere
|
|
|
|
|
*
|
|
|
|
|
* ```adblock
|
|
|
|
|
* *##+js(prevent-canvas, /webgl/)
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* 3. Prevent `example.com` from accessing any flavor of canvas API except `2d`
|
|
|
|
|
*
|
|
|
|
|
* ```adblock
|
|
|
|
|
* example.com##+js(prevent-canvas, !2d)
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* ### References
|
|
|
|
|
*
|
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
|
|
|
|
|
*
|
|
|
|
|
* */
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'prevent-canvas.js',
|
|
|
|
|
fn: preventCanvas,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function preventCanvas(
|
|
|
|
|
contextType = ''
|
|
|
|
|
) {
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const pattern = safe.initPattern(contextType, { canNegate: true });
|
|
|
|
|
const proto = globalThis.HTMLCanvasElement.prototype;
|
|
|
|
|
proto.getContext = new Proxy(proto.getContext, {
|
|
|
|
|
apply(target, thisArg, args) {
|
|
|
|
|
if ( safe.testPattern(pattern, args[0]) ) { return null; }
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-29 18:51:44 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'multiup.js',
|
|
|
|
|
fn: multiup,
|
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
});
|
|
|
|
|
function multiup() {
|
|
|
|
|
const handler = ev => {
|
|
|
|
|
const target = ev.target;
|
|
|
|
|
if ( target.matches('button[link]') === false ) { return; }
|
|
|
|
|
const ancestor = target.closest('form');
|
|
|
|
|
if ( ancestor === null ) { return; }
|
|
|
|
|
if ( ancestor !== target.parentElement ) { return; }
|
|
|
|
|
const link = (target.getAttribute('link') || '').trim();
|
|
|
|
|
if ( link === '' ) { return; }
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
document.location.href = link;
|
|
|
|
|
};
|
|
|
|
|
document.addEventListener('click', handler, { capture: true });
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-28 00:17:28 +01:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'remove-cache-storage-item.js',
|
|
|
|
|
fn: removeCacheStorageItem,
|
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function removeCacheStorageItem(
|
|
|
|
|
cacheNamePattern = '',
|
|
|
|
|
requestPattern = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( cacheNamePattern === '' ) { return; }
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('remove-cache-storage-item', cacheNamePattern, requestPattern);
|
|
|
|
|
const cacheStorage = self.caches;
|
|
|
|
|
if ( cacheStorage instanceof Object === false ) { return; }
|
|
|
|
|
const reCache = safe.patternToRegex(cacheNamePattern, undefined, true);
|
|
|
|
|
const reRequest = safe.patternToRegex(requestPattern, undefined, true);
|
|
|
|
|
cacheStorage.keys().then(cacheNames => {
|
|
|
|
|
for ( const cacheName of cacheNames ) {
|
|
|
|
|
if ( reCache.test(cacheName) === false ) { continue; }
|
|
|
|
|
if ( requestPattern === '' ) {
|
|
|
|
|
cacheStorage.delete(cacheName).then(result => {
|
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Deleting ${cacheName}`);
|
|
|
|
|
}
|
|
|
|
|
if ( result !== true ) { return; }
|
|
|
|
|
safe.uboLog(logPrefix, `Deleted ${cacheName}: ${result}`);
|
|
|
|
|
});
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
cacheStorage.open(cacheName).then(cache => {
|
|
|
|
|
cache.keys().then(requests => {
|
|
|
|
|
for ( const request of requests ) {
|
|
|
|
|
if ( reRequest.test(request.url) === false ) { continue; }
|
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Deleting ${cacheName}/${request.url}`);
|
|
|
|
|
}
|
|
|
|
|
cache.delete(request).then(result => {
|
|
|
|
|
if ( result !== true ) { return; }
|
|
|
|
|
safe.uboLog(logPrefix, `Deleted ${cacheName}/${request.url}: ${result}`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-04 13:13:22 +02:00
|
|
|
|
|
2023-05-23 15:03:19 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* Scriplets below this section are only available for filter lists from
|
|
|
|
|
* trusted sources. They all have the property `requiresTrust` set to `true`.
|
|
|
|
|
*
|
|
|
|
|
* Trusted sources are:
|
|
|
|
|
*
|
|
|
|
|
* - uBO's own filter lists, which name starts with "uBlock filters – ", and
|
|
|
|
|
* maintained at: https://github.com/uBlockOrigin/uAssets
|
|
|
|
|
*
|
|
|
|
|
* - The user's own filters as seen in "My filters" pane in uBO's dashboard.
|
|
|
|
|
*
|
|
|
|
|
* The trustworthiness of filters using these privileged scriptlets are
|
|
|
|
|
* evaluated at filter list compiled time: when a filter using one of the
|
|
|
|
|
* privileged scriptlet originates from a non-trusted filter list source, it
|
|
|
|
|
* is discarded at compile time, specifically from within:
|
|
|
|
|
*
|
|
|
|
|
* - Source: ./src/js/scriptlet-filtering.js
|
|
|
|
|
* - Method: scriptletFilteringEngine.compile(), via normalizeRawFilter()
|
|
|
|
|
*
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
2023-05-24 21:56:42 +02:00
|
|
|
|
* replace-node-text.js
|
2023-05-23 15:03:19 +02:00
|
|
|
|
*
|
|
|
|
|
* Replace text instance(s) with another text instance inside specific
|
|
|
|
|
* DOM nodes. By default, the scriplet stops and quits at the interactive
|
|
|
|
|
* stage of a document.
|
|
|
|
|
*
|
|
|
|
|
* See commit messages for usage:
|
|
|
|
|
* - https://github.com/gorhill/uBlock/commit/99ce027fd702
|
|
|
|
|
* - https://github.com/gorhill/uBlock/commit/41876336db48
|
|
|
|
|
*
|
|
|
|
|
**/
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
2023-11-14 19:53:29 +01:00
|
|
|
|
name: 'trusted-replace-node-text.js',
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
requiresTrust: true,
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
2023-11-14 19:53:29 +01:00
|
|
|
|
'trusted-rpnt.js',
|
|
|
|
|
'replace-node-text.js',
|
2023-06-29 01:35:22 +02:00
|
|
|
|
'rpnt.js',
|
|
|
|
|
],
|
2023-05-24 20:58:12 +02:00
|
|
|
|
fn: replaceNodeText,
|
2023-05-23 02:19:00 +02:00
|
|
|
|
world: 'ISOLATED',
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
dependencies: [
|
2023-10-17 23:33:49 +02:00
|
|
|
|
'replace-node-text.fn',
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
],
|
|
|
|
|
});
|
2023-05-24 20:58:12 +02:00
|
|
|
|
function replaceNodeText(
|
2023-05-25 14:51:26 +02:00
|
|
|
|
nodeName,
|
|
|
|
|
pattern,
|
|
|
|
|
replacement,
|
|
|
|
|
...extraArgs
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
) {
|
2023-10-17 23:33:49 +02:00
|
|
|
|
replaceNodeTextFn(nodeName, pattern, replacement, ...extraArgs);
|
Add trusted-source support for privileged scriptlets
At the moment, the only filter lists deemed from a "trusted source"
are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and
the user's own filters from "My filters".
A new scriptlet which can only be used by filter lists from trusted
sources has been introduced: `sed.js`.
The new `sed.js` scriptlet provides the ability to perform
text-level substitutions. Usage:
example.org##+js(sed, nodeName, pattern, replacement, ...)
`nodeName`
The name of the node for which the text content must be substituted.
Valid node names can be found at:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
`pattern`
A string or regex to find in the text content of the node as the target of
substitution.
`replacement`
The replacement text. Can be omitted if the goal is to delete the text which
matches the pattern. Cannot be omitted if extra pairs of parameters have to be
used (see below).
Optionally, extra pairs of parameters to modify the behavior of the scriptlet:
`condition, pattern`
A string or regex which must be found in the text content of the node
in order for the substitution to occur.
`sedCount, n`
This will cause the scriptlet to stop after n instances of substitution. Since
a mutation oberver is used by the scriptlet, it's advised to stop it whenever
it becomes pointless. Default to zero, which means the scriptlet never stops.
`tryCount, n`
This will cause the scriptlet to stop after n instances of mutation observer
run (regardless of whether a substitution occurred). Default to zero, which
means the scriptlet never stops.
`log, 1`
This will cause the scriptlet to output information at the console, useful as
a debugging tool for filter authors. The logging ability is supported only
in the dev build of uBO.
Examples of usage:
example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1)
example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 20:16:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-24 21:56:42 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* trusted-set-constant.js
|
|
|
|
|
*
|
|
|
|
|
* Set specified property to any value. This is essentially the same as
|
|
|
|
|
* set-constant.js, but with no restriction as to which values can be used.
|
|
|
|
|
*
|
|
|
|
|
**/
|
2023-05-23 16:59:27 +02:00
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-set-constant.js',
|
|
|
|
|
requiresTrust: true,
|
2023-06-29 01:35:22 +02:00
|
|
|
|
aliases: [
|
|
|
|
|
'trusted-set.js',
|
|
|
|
|
],
|
2023-05-23 16:59:27 +02:00
|
|
|
|
fn: trustedSetConstant,
|
|
|
|
|
dependencies: [
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
'set-constant.fn'
|
2023-05-23 16:59:27 +02:00
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function trustedSetConstant(
|
|
|
|
|
...args
|
|
|
|
|
) {
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
setConstantFn(true, ...args);
|
2023-05-23 16:59:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-15 17:08:35 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
2023-06-16 01:57:10 +02:00
|
|
|
|
* trusted-set-cookie.js
|
2023-06-15 17:08:35 +02:00
|
|
|
|
*
|
2023-06-16 01:57:10 +02:00
|
|
|
|
* Set specified cookie to an arbitrary value.
|
2023-06-15 17:08:35 +02:00
|
|
|
|
*
|
|
|
|
|
* Reference:
|
2023-06-16 01:57:10 +02:00
|
|
|
|
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-set-cookie.js#L23
|
2023-06-15 17:08:35 +02:00
|
|
|
|
*
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
2023-06-16 01:57:10 +02:00
|
|
|
|
name: 'trusted-set-cookie.js',
|
2023-06-15 17:08:35 +02:00
|
|
|
|
requiresTrust: true,
|
2023-06-16 01:57:10 +02:00
|
|
|
|
fn: trustedSetCookie,
|
2023-06-15 17:08:35 +02:00
|
|
|
|
world: 'ISOLATED',
|
2023-06-16 17:32:12 +02:00
|
|
|
|
dependencies: [
|
2023-08-08 13:41:21 +02:00
|
|
|
|
'safe-self.fn',
|
2023-10-23 00:19:18 +02:00
|
|
|
|
'set-cookie.fn',
|
2023-06-16 17:32:12 +02:00
|
|
|
|
],
|
2023-06-15 17:08:35 +02:00
|
|
|
|
});
|
2023-06-16 01:57:10 +02:00
|
|
|
|
function trustedSetCookie(
|
2023-06-15 17:08:35 +02:00
|
|
|
|
name = '',
|
|
|
|
|
value = '',
|
2023-06-16 01:57:10 +02:00
|
|
|
|
offsetExpiresSec = '',
|
2023-06-16 17:32:12 +02:00
|
|
|
|
path = ''
|
2023-06-15 17:08:35 +02:00
|
|
|
|
) {
|
|
|
|
|
if ( name === '' ) { return; }
|
2023-06-16 17:32:12 +02:00
|
|
|
|
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
|
2023-06-16 01:57:10 +02:00
|
|
|
|
const time = new Date();
|
2023-06-16 17:32:12 +02:00
|
|
|
|
|
2023-06-16 01:57:10 +02:00
|
|
|
|
if ( value === '$now$' ) {
|
|
|
|
|
value = Date.now();
|
|
|
|
|
} else if ( value === '$currentDate$' ) {
|
|
|
|
|
value = time.toUTCString();
|
|
|
|
|
}
|
2023-06-16 17:32:12 +02:00
|
|
|
|
|
|
|
|
|
let expires = '';
|
2023-06-16 01:57:10 +02:00
|
|
|
|
if ( offsetExpiresSec !== '' ) {
|
|
|
|
|
if ( offsetExpiresSec === '1day' ) {
|
|
|
|
|
time.setDate(time.getDate() + 1);
|
|
|
|
|
} else if ( offsetExpiresSec === '1year' ) {
|
|
|
|
|
time.setFullYear(time.getFullYear() + 1);
|
|
|
|
|
} else {
|
|
|
|
|
if ( /^\d+$/.test(offsetExpiresSec) === false ) { return; }
|
|
|
|
|
time.setSeconds(time.getSeconds() + parseInt(offsetExpiresSec, 10));
|
|
|
|
|
}
|
2023-06-16 17:32:12 +02:00
|
|
|
|
expires = time.toUTCString();
|
2023-06-16 02:00:57 +02:00
|
|
|
|
}
|
2023-06-16 17:32:12 +02:00
|
|
|
|
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const done = setCookieFn(
|
2023-10-23 00:19:18 +02:00
|
|
|
|
true,
|
2023-06-16 17:32:12 +02:00
|
|
|
|
name,
|
|
|
|
|
value,
|
|
|
|
|
expires,
|
|
|
|
|
path,
|
2023-08-08 13:41:21 +02:00
|
|
|
|
safeSelf().getExtraArgs(Array.from(arguments), 4)
|
2023-06-16 17:32:12 +02:00
|
|
|
|
);
|
2024-01-25 18:20:38 +01:00
|
|
|
|
|
|
|
|
|
if ( done ) {
|
|
|
|
|
safe.uboLog(logPrefix, 'Done');
|
|
|
|
|
}
|
2023-06-15 17:08:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-10-18 15:50:58 +02:00
|
|
|
|
// For compatibility with AdGuard
|
2023-10-18 15:48:08 +02:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-set-cookie-reload.js',
|
|
|
|
|
requiresTrust: true,
|
|
|
|
|
fn: trustedSetCookieReload,
|
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
|
|
|
|
'trusted-set-cookie.js',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) {
|
|
|
|
|
trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 16:58:10 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* trusted-set-local-storage-item.js
|
|
|
|
|
*
|
|
|
|
|
* Set a local storage entry to an arbitrary value.
|
|
|
|
|
*
|
|
|
|
|
* Reference:
|
|
|
|
|
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-set-local-storage-item.js
|
|
|
|
|
*
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-set-local-storage-item.js',
|
|
|
|
|
requiresTrust: true,
|
|
|
|
|
fn: trustedSetLocalStorageItem,
|
|
|
|
|
world: 'ISOLATED',
|
2023-07-04 13:13:22 +02:00
|
|
|
|
dependencies: [
|
2023-10-21 02:10:35 +02:00
|
|
|
|
'set-local-storage-item.fn',
|
2023-07-04 13:13:22 +02:00
|
|
|
|
],
|
2023-06-20 16:58:10 +02:00
|
|
|
|
});
|
2023-07-04 13:13:22 +02:00
|
|
|
|
function trustedSetLocalStorageItem(key = '', value = '') {
|
2023-10-21 02:10:35 +02:00
|
|
|
|
setLocalStorageItemFn('local', true, key, value);
|
2023-06-20 16:58:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-13 16:36:55 +01:00
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-set-session-storage-item.js',
|
|
|
|
|
requiresTrust: true,
|
|
|
|
|
fn: trustedSetSessionStorageItem,
|
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
|
|
|
|
'set-local-storage-item.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function trustedSetSessionStorageItem(key = '', value = '') {
|
|
|
|
|
setLocalStorageItemFn('session', true, key, value);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-05 17:55:47 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* trusted-replace-fetch-response.js
|
|
|
|
|
*
|
|
|
|
|
* Replaces response text content of fetch requests if all given parameters
|
|
|
|
|
* match.
|
|
|
|
|
*
|
|
|
|
|
* Reference:
|
|
|
|
|
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-replace-fetch-response.js
|
|
|
|
|
*
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-replace-fetch-response.js',
|
|
|
|
|
requiresTrust: true,
|
2024-01-20 16:33:36 +01:00
|
|
|
|
aliases: [
|
|
|
|
|
'trusted-rpfr.js',
|
|
|
|
|
],
|
2023-08-05 17:55:47 +02:00
|
|
|
|
fn: trustedReplaceFetchResponse,
|
|
|
|
|
dependencies: [
|
2023-10-07 17:44:18 +02:00
|
|
|
|
'replace-fetch-response.fn',
|
2023-08-05 17:55:47 +02:00
|
|
|
|
],
|
|
|
|
|
});
|
2023-10-07 17:44:18 +02:00
|
|
|
|
function trustedReplaceFetchResponse(...args) {
|
|
|
|
|
replaceFetchResponseFn(true, ...args);
|
2023-08-05 17:55:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 16:59:27 +02:00
|
|
|
|
/******************************************************************************/
|
2023-09-04 20:54:57 +02:00
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-replace-xhr-response.js',
|
2023-09-05 20:11:33 +02:00
|
|
|
|
requiresTrust: true,
|
2023-09-04 20:54:57 +02:00
|
|
|
|
fn: trustedReplaceXhrResponse,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'match-object-properties.fn',
|
|
|
|
|
'parse-properties-to-match.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function trustedReplaceXhrResponse(
|
|
|
|
|
pattern = '',
|
|
|
|
|
replacement = '',
|
|
|
|
|
propsToMatch = ''
|
|
|
|
|
) {
|
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('trusted-replace-xhr-response', pattern, replacement, propsToMatch);
|
2023-09-04 20:54:57 +02:00
|
|
|
|
const xhrInstances = new WeakMap();
|
|
|
|
|
if ( pattern === '*' ) { pattern = '.*'; }
|
|
|
|
|
const rePattern = safe.patternToRegex(pattern);
|
|
|
|
|
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
|
|
|
|
|
self.XMLHttpRequest = class extends self.XMLHttpRequest {
|
|
|
|
|
open(method, url, ...args) {
|
|
|
|
|
const outerXhr = this;
|
2023-09-05 20:11:33 +02:00
|
|
|
|
const xhrDetails = { method, url };
|
2023-09-04 20:54:57 +02:00
|
|
|
|
let outcome = 'match';
|
|
|
|
|
if ( propNeedles.size !== 0 ) {
|
2023-09-05 20:11:33 +02:00
|
|
|
|
if ( matchObjectProperties(propNeedles, xhrDetails) === false ) {
|
2023-09-04 20:54:57 +02:00
|
|
|
|
outcome = 'nomatch';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( outcome === 'match' ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, `Matched "propsToMatch"`);
|
|
|
|
|
}
|
2023-09-04 20:54:57 +02:00
|
|
|
|
xhrInstances.set(outerXhr, xhrDetails);
|
|
|
|
|
}
|
|
|
|
|
return super.open(method, url, ...args);
|
|
|
|
|
}
|
2023-09-05 20:11:33 +02:00
|
|
|
|
get response() {
|
|
|
|
|
const innerResponse = super.response;
|
|
|
|
|
const xhrDetails = xhrInstances.get(this);
|
2023-09-04 20:54:57 +02:00
|
|
|
|
if ( xhrDetails === undefined ) {
|
2023-09-05 20:11:33 +02:00
|
|
|
|
return innerResponse;
|
2023-09-04 20:54:57 +02:00
|
|
|
|
}
|
2023-09-22 15:33:02 +02:00
|
|
|
|
const responseLength = typeof innerResponse === 'string'
|
|
|
|
|
? innerResponse.length
|
|
|
|
|
: undefined;
|
|
|
|
|
if ( xhrDetails.lastResponseLength !== responseLength ) {
|
|
|
|
|
xhrDetails.response = undefined;
|
|
|
|
|
xhrDetails.lastResponseLength = responseLength;
|
2023-09-05 20:11:33 +02:00
|
|
|
|
}
|
2023-09-22 15:33:02 +02:00
|
|
|
|
if ( xhrDetails.response !== undefined ) {
|
|
|
|
|
return xhrDetails.response;
|
|
|
|
|
}
|
|
|
|
|
if ( typeof innerResponse !== 'string' ) {
|
|
|
|
|
return (xhrDetails.response = innerResponse);
|
2023-09-05 20:11:33 +02:00
|
|
|
|
}
|
|
|
|
|
const textBefore = innerResponse;
|
|
|
|
|
const textAfter = textBefore.replace(rePattern, replacement);
|
2024-01-25 18:20:38 +01:00
|
|
|
|
if ( textAfter !== textBefore ) {
|
|
|
|
|
safe.uboLog(logPrefix, 'Match');
|
2023-09-05 20:11:33 +02:00
|
|
|
|
}
|
2023-09-22 15:33:02 +02:00
|
|
|
|
return (xhrDetails.response = textAfter);
|
2023-09-05 20:11:33 +02:00
|
|
|
|
}
|
|
|
|
|
get responseText() {
|
2023-09-14 17:13:58 +02:00
|
|
|
|
const response = this.response;
|
|
|
|
|
if ( typeof response !== 'string' ) {
|
|
|
|
|
return super.responseText;
|
|
|
|
|
}
|
|
|
|
|
return response;
|
2023-09-04 20:54:57 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-15 17:08:15 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* trusted-click-element.js
|
|
|
|
|
*
|
|
|
|
|
* Reference API:
|
|
|
|
|
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-click-element.js
|
|
|
|
|
*
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-click-element.js',
|
|
|
|
|
requiresTrust: true,
|
|
|
|
|
fn: trustedClickElement,
|
|
|
|
|
world: 'ISOLATED',
|
|
|
|
|
dependencies: [
|
2024-01-20 16:33:36 +01:00
|
|
|
|
'get-all-cookies.fn',
|
|
|
|
|
'get-all-local-storage.fn',
|
2023-10-15 17:08:15 +02:00
|
|
|
|
'run-at-html-element.fn',
|
2023-10-17 01:53:48 +02:00
|
|
|
|
'safe-self.fn',
|
2023-10-15 17:08:15 +02:00
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function trustedClickElement(
|
|
|
|
|
selectors = '',
|
2024-01-20 16:33:36 +01:00
|
|
|
|
extraMatch = '',
|
2023-10-15 17:08:15 +02:00
|
|
|
|
delay = ''
|
|
|
|
|
) {
|
2023-10-17 01:53:48 +02:00
|
|
|
|
const safe = safeSelf();
|
2024-01-25 18:20:38 +01:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('trusted-click-element', selectors, extraMatch, delay);
|
2023-10-17 01:53:48 +02:00
|
|
|
|
|
2024-01-20 16:33:36 +01:00
|
|
|
|
if ( extraMatch !== '' ) {
|
|
|
|
|
const assertions = extraMatch.split(',').map(s => {
|
|
|
|
|
const pos1 = s.indexOf(':');
|
|
|
|
|
const s1 = pos1 !== -1 ? s.slice(0, pos1) : s;
|
|
|
|
|
const not = s1.startsWith('!');
|
|
|
|
|
const type = not ? s1.slice(1) : s1;
|
|
|
|
|
const s2 = pos1 !== -1 ? s.slice(pos1+1).trim() : '';
|
|
|
|
|
if ( s2 === '' ) { return; }
|
|
|
|
|
const out = { not, type };
|
|
|
|
|
const match = /^\/(.+)\/(i?)$/.exec(s2);
|
|
|
|
|
if ( match !== null ) {
|
|
|
|
|
out.re = new RegExp(match[1], match[2] || undefined);
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
const pos2 = s2.indexOf('=');
|
|
|
|
|
const key = pos2 !== -1 ? s2.slice(0, pos2).trim() : s2;
|
|
|
|
|
const value = pos2 !== -1 ? s2.slice(pos2+1).trim() : '';
|
|
|
|
|
out.re = new RegExp(`^${this.escapeRegexChars(key)}=${this.escapeRegexChars(value)}`);
|
|
|
|
|
return out;
|
|
|
|
|
}).filter(details => details !== undefined);
|
|
|
|
|
const allCookies = assertions.some(o => o.type === 'cookie')
|
|
|
|
|
? getAllCookiesFn()
|
|
|
|
|
: [];
|
|
|
|
|
const allStorageItems = assertions.some(o => o.type === 'localStorage')
|
|
|
|
|
? getAllLocalStorageFn()
|
|
|
|
|
: [];
|
|
|
|
|
const hasNeedle = (haystack, needle) => {
|
|
|
|
|
for ( const { key, value } of haystack ) {
|
|
|
|
|
if ( needle.test(`${key}=${value}`) ) { return true; }
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
for ( const { not, type, re } of assertions ) {
|
|
|
|
|
switch ( type ) {
|
|
|
|
|
case 'cookie':
|
|
|
|
|
if ( hasNeedle(allCookies, re) === not ) { return; }
|
|
|
|
|
break;
|
|
|
|
|
case 'localStorage':
|
|
|
|
|
if ( hasNeedle(allStorageItems, re) === not ) { return; }
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-04 14:02:07 +01:00
|
|
|
|
const querySelectorEx = (selector, context = document) => {
|
|
|
|
|
const pos = selector.indexOf(' >>> ');
|
|
|
|
|
if ( pos === -1 ) { return context.querySelector(selector); }
|
|
|
|
|
const outside = selector.slice(0, pos).trim();
|
|
|
|
|
const inside = selector.slice(pos + 5).trim();
|
|
|
|
|
const elem = context.querySelector(outside);
|
|
|
|
|
if ( elem === null ) { return null; }
|
|
|
|
|
const shadowRoot = elem.shadowRoot;
|
|
|
|
|
return shadowRoot && querySelectorEx(inside, shadowRoot);
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-15 17:08:15 +02:00
|
|
|
|
const selectorList = selectors.split(/\s*,\s*/)
|
|
|
|
|
.filter(s => {
|
|
|
|
|
try {
|
2023-12-04 14:02:07 +01:00
|
|
|
|
void querySelectorEx(s);
|
2023-10-15 17:08:15 +02:00
|
|
|
|
} catch(_) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
if ( selectorList.length === 0 ) { return; }
|
|
|
|
|
|
|
|
|
|
const clickDelay = parseInt(delay, 10) || 1;
|
|
|
|
|
const t0 = Date.now();
|
|
|
|
|
const tbye = t0 + 10000;
|
2023-10-19 23:23:05 +02:00
|
|
|
|
let tnext = selectorList.length !== 1 ? t0 : t0 + clickDelay;
|
2023-10-15 17:08:15 +02:00
|
|
|
|
|
|
|
|
|
const terminate = ( ) => {
|
|
|
|
|
selectorList.length = 0;
|
|
|
|
|
next.stop();
|
|
|
|
|
observe.stop();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const next = notFound => {
|
2023-10-17 01:53:48 +02:00
|
|
|
|
if ( selectorList.length === 0 ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'Completed');
|
2023-10-17 01:53:48 +02:00
|
|
|
|
return terminate();
|
|
|
|
|
}
|
2023-10-15 17:08:15 +02:00
|
|
|
|
const tnow = Date.now();
|
2023-10-17 01:53:48 +02:00
|
|
|
|
if ( tnow >= tbye ) {
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, 'Timed out');
|
2023-10-17 01:53:48 +02:00
|
|
|
|
return terminate();
|
|
|
|
|
}
|
2023-10-15 17:08:15 +02:00
|
|
|
|
if ( notFound ) { observe(); }
|
2023-10-19 23:23:05 +02:00
|
|
|
|
const delay = Math.max(notFound ? tbye - tnow : tnext - tnow, 1);
|
2023-10-15 17:08:15 +02:00
|
|
|
|
next.timer = setTimeout(( ) => {
|
|
|
|
|
next.timer = undefined;
|
|
|
|
|
process();
|
|
|
|
|
}, delay);
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Waiting for ${selectorList[0]}...`);
|
2023-10-15 17:08:15 +02:00
|
|
|
|
};
|
|
|
|
|
next.stop = ( ) => {
|
|
|
|
|
if ( next.timer === undefined ) { return; }
|
|
|
|
|
clearTimeout(next.timer);
|
|
|
|
|
next.timer = undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const observe = ( ) => {
|
|
|
|
|
if ( observe.observer !== undefined ) { return; }
|
|
|
|
|
observe.observer = new MutationObserver(( ) => {
|
|
|
|
|
if ( observe.timer !== undefined ) { return; }
|
|
|
|
|
observe.timer = setTimeout(( ) => {
|
|
|
|
|
observe.timer = undefined;
|
|
|
|
|
process();
|
|
|
|
|
}, 20);
|
|
|
|
|
});
|
|
|
|
|
observe.observer.observe(document, {
|
|
|
|
|
attributes: true,
|
|
|
|
|
childList: true,
|
|
|
|
|
subtree: true,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
observe.stop = ( ) => {
|
|
|
|
|
if ( observe.timer !== undefined ) {
|
|
|
|
|
clearTimeout(observe.timer);
|
|
|
|
|
observe.timer = undefined;
|
|
|
|
|
}
|
|
|
|
|
if ( observe.observer ) {
|
|
|
|
|
observe.observer.disconnect();
|
|
|
|
|
observe.observer = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const process = ( ) => {
|
|
|
|
|
next.stop();
|
|
|
|
|
if ( Date.now() < tnext ) { return next(); }
|
|
|
|
|
const selector = selectorList.shift();
|
|
|
|
|
if ( selector === undefined ) { return terminate(); }
|
2023-12-04 14:02:07 +01:00
|
|
|
|
const elem = querySelectorEx(selector);
|
2023-10-15 17:08:15 +02:00
|
|
|
|
if ( elem === null ) {
|
|
|
|
|
selectorList.unshift(selector);
|
|
|
|
|
return next(true);
|
|
|
|
|
}
|
2024-01-25 18:20:38 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Clicked ${selector}`);
|
2023-10-15 22:40:35 +02:00
|
|
|
|
elem.click();
|
2023-10-15 17:08:15 +02:00
|
|
|
|
tnext += clickDelay;
|
|
|
|
|
next();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
runAtHtmlElementFn(process);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-04 20:54:57 +02:00
|
|
|
|
/******************************************************************************/
|
Add `trusted-prune-inbound-object` scriptlet
As per discussion with filter list maintainers.
To perform object pruning for any given call which has an object
as argument (hence "inbound").
Since `json-prune-stringify` scriptlet is a specific form of
pruning inbound objects, it has been removed.
The arguments for `trusted-prune-inbound-object` in order are:
- The name of the property to trap. Must be a function, and must
exist when the scriptlet tries to install the trap.
- The position of the object to prune in the argument list when
the trapped function is called. The position is 1-based and
must be an integer greater than 0.
- The properties to prune (as with `json-prune`)
- The properties which must all be present for pruning to occur
(as with `json-prune`)
- Varargs:
- `, dontOverwrite, 1`: do not modify the target inbound object
Examples:
Remove `title` and `name` properties before passing the object to
`JSON.stringify` call:
example.org##+js(trusted-prune-inbound-object, JSON.stringify, 1, title name)
Remove `status` property before passing the object to `Object.keys`
call but do not modify caller's instance of the object:
example.org##+js(trusted-prune-inbound-object, Object.keys, 1, status, , dontOverwrite, 1)
2023-10-21 15:31:50 +02:00
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-prune-inbound-object.js',
|
|
|
|
|
requiresTrust: true,
|
|
|
|
|
fn: trustedPruneInboundObject,
|
|
|
|
|
dependencies: [
|
2023-10-28 13:35:38 +02:00
|
|
|
|
'object-find-owner.fn',
|
Add `trusted-prune-inbound-object` scriptlet
As per discussion with filter list maintainers.
To perform object pruning for any given call which has an object
as argument (hence "inbound").
Since `json-prune-stringify` scriptlet is a specific form of
pruning inbound objects, it has been removed.
The arguments for `trusted-prune-inbound-object` in order are:
- The name of the property to trap. Must be a function, and must
exist when the scriptlet tries to install the trap.
- The position of the object to prune in the argument list when
the trapped function is called. The position is 1-based and
must be an integer greater than 0.
- The properties to prune (as with `json-prune`)
- The properties which must all be present for pruning to occur
(as with `json-prune`)
- Varargs:
- `, dontOverwrite, 1`: do not modify the target inbound object
Examples:
Remove `title` and `name` properties before passing the object to
`JSON.stringify` call:
example.org##+js(trusted-prune-inbound-object, JSON.stringify, 1, title name)
Remove `status` property before passing the object to `Object.keys`
call but do not modify caller's instance of the object:
example.org##+js(trusted-prune-inbound-object, Object.keys, 1, status, , dontOverwrite, 1)
2023-10-21 15:31:50 +02:00
|
|
|
|
'object-prune.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function trustedPruneInboundObject(
|
|
|
|
|
entryPoint = '',
|
|
|
|
|
argPos = '',
|
|
|
|
|
rawPrunePaths = '',
|
|
|
|
|
rawNeedlePaths = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( entryPoint === '' ) { return; }
|
|
|
|
|
let context = globalThis;
|
|
|
|
|
let prop = entryPoint;
|
|
|
|
|
for (;;) {
|
|
|
|
|
const pos = prop.indexOf('.');
|
|
|
|
|
if ( pos === -1 ) { break; }
|
|
|
|
|
context = context[prop.slice(0, pos)];
|
|
|
|
|
if ( context instanceof Object === false ) { return; }
|
|
|
|
|
prop = prop.slice(pos+1);
|
|
|
|
|
}
|
|
|
|
|
if ( typeof context[prop] !== 'function' ) { return; }
|
|
|
|
|
const argIndex = parseInt(argPos);
|
|
|
|
|
if ( isNaN(argIndex) ) { return; }
|
|
|
|
|
if ( argIndex < 1 ) { return; }
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 4);
|
2023-10-28 13:35:38 +02:00
|
|
|
|
const needlePaths = [];
|
|
|
|
|
if ( rawPrunePaths !== '' ) {
|
|
|
|
|
needlePaths.push(...rawPrunePaths.split(/ +/));
|
|
|
|
|
}
|
|
|
|
|
if ( rawNeedlePaths !== '' ) {
|
|
|
|
|
needlePaths.push(...rawNeedlePaths.split(/ +/));
|
|
|
|
|
}
|
2023-11-10 01:57:51 +01:00
|
|
|
|
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
|
2023-10-28 13:35:38 +02:00
|
|
|
|
const mustProcess = root => {
|
|
|
|
|
for ( const needlePath of needlePaths ) {
|
|
|
|
|
if ( objectFindOwnerFn(root, needlePath) === false ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
Add `trusted-prune-inbound-object` scriptlet
As per discussion with filter list maintainers.
To perform object pruning for any given call which has an object
as argument (hence "inbound").
Since `json-prune-stringify` scriptlet is a specific form of
pruning inbound objects, it has been removed.
The arguments for `trusted-prune-inbound-object` in order are:
- The name of the property to trap. Must be a function, and must
exist when the scriptlet tries to install the trap.
- The position of the object to prune in the argument list when
the trapped function is called. The position is 1-based and
must be an integer greater than 0.
- The properties to prune (as with `json-prune`)
- The properties which must all be present for pruning to occur
(as with `json-prune`)
- Varargs:
- `, dontOverwrite, 1`: do not modify the target inbound object
Examples:
Remove `title` and `name` properties before passing the object to
`JSON.stringify` call:
example.org##+js(trusted-prune-inbound-object, JSON.stringify, 1, title name)
Remove `status` property before passing the object to `Object.keys`
call but do not modify caller's instance of the object:
example.org##+js(trusted-prune-inbound-object, Object.keys, 1, status, , dontOverwrite, 1)
2023-10-21 15:31:50 +02:00
|
|
|
|
context[prop] = new Proxy(context[prop], {
|
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
|
const targetArg = argIndex <= args.length
|
|
|
|
|
? args[argIndex-1]
|
|
|
|
|
: undefined;
|
2023-10-28 13:35:38 +02:00
|
|
|
|
if ( targetArg instanceof Object && mustProcess(targetArg) ) {
|
|
|
|
|
let objBefore = targetArg;
|
|
|
|
|
if ( extraArgs.dontOverwrite ) {
|
|
|
|
|
try {
|
|
|
|
|
objBefore = safe.JSON_parse(safe.JSON_stringify(targetArg));
|
|
|
|
|
} catch(_) {
|
|
|
|
|
objBefore = undefined;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( objBefore !== undefined ) {
|
|
|
|
|
const objAfter = objectPruneFn(
|
|
|
|
|
objBefore,
|
|
|
|
|
rawPrunePaths,
|
|
|
|
|
rawNeedlePaths,
|
2023-11-10 01:57:51 +01:00
|
|
|
|
stackNeedle,
|
2023-10-28 13:35:38 +02:00
|
|
|
|
extraArgs
|
|
|
|
|
);
|
|
|
|
|
args[argIndex-1] = objAfter || objBefore;
|
|
|
|
|
}
|
Add `trusted-prune-inbound-object` scriptlet
As per discussion with filter list maintainers.
To perform object pruning for any given call which has an object
as argument (hence "inbound").
Since `json-prune-stringify` scriptlet is a specific form of
pruning inbound objects, it has been removed.
The arguments for `trusted-prune-inbound-object` in order are:
- The name of the property to trap. Must be a function, and must
exist when the scriptlet tries to install the trap.
- The position of the object to prune in the argument list when
the trapped function is called. The position is 1-based and
must be an integer greater than 0.
- The properties to prune (as with `json-prune`)
- The properties which must all be present for pruning to occur
(as with `json-prune`)
- Varargs:
- `, dontOverwrite, 1`: do not modify the target inbound object
Examples:
Remove `title` and `name` properties before passing the object to
`JSON.stringify` call:
example.org##+js(trusted-prune-inbound-object, JSON.stringify, 1, title name)
Remove `status` property before passing the object to `Object.keys`
call but do not modify caller's instance of the object:
example.org##+js(trusted-prune-inbound-object, Object.keys, 1, status, , dontOverwrite, 1)
2023-10-21 15:31:50 +02:00
|
|
|
|
}
|
|
|
|
|
return Reflect.apply(target, thisArg, args);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
2023-10-22 18:35:49 +02:00
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-prune-outbound-object.js',
|
|
|
|
|
requiresTrust: true,
|
|
|
|
|
fn: trustedPruneOutboundObject,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'object-prune.fn',
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
'proxy-apply.fn',
|
2023-10-22 18:35:49 +02:00
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function trustedPruneOutboundObject(
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
propChain = '',
|
2023-10-22 18:35:49 +02:00
|
|
|
|
rawPrunePaths = '',
|
|
|
|
|
rawNeedlePaths = ''
|
|
|
|
|
) {
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
if ( propChain === '' ) { return; }
|
2023-10-22 18:35:49 +02:00
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
const reflector = proxyApplyFn(propChain, function(...args) {
|
|
|
|
|
const objBefore = reflector(...args);
|
|
|
|
|
if ( objBefore instanceof Object === false ) { return objBefore; }
|
|
|
|
|
const objAfter = objectPruneFn(
|
|
|
|
|
objBefore,
|
|
|
|
|
rawPrunePaths,
|
|
|
|
|
rawNeedlePaths,
|
|
|
|
|
{ matchAll: true },
|
|
|
|
|
extraArgs
|
|
|
|
|
);
|
|
|
|
|
return objAfter || objBefore;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-replace-argument.js',
|
|
|
|
|
requiresTrust: true,
|
|
|
|
|
fn: trustedReplaceArgument,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'proxy-apply.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
'validate-constant.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function trustedReplaceArgument(
|
|
|
|
|
propChain = '',
|
|
|
|
|
argpos = '',
|
|
|
|
|
argraw = ''
|
|
|
|
|
) {
|
|
|
|
|
if ( propChain === '' ) { return; }
|
|
|
|
|
if ( argpos === '' ) { return; }
|
|
|
|
|
if ( argraw === '' ) { return; }
|
|
|
|
|
const safe = safeSelf();
|
|
|
|
|
const logPrefix = safe.makeLogPrefix('trusted-replace-argument', propChain, argpos, argraw);
|
|
|
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
|
|
|
|
const normalValue = validateConstantFn(true, argraw);
|
|
|
|
|
const reCondition = extraArgs.condition
|
|
|
|
|
? safe.patternToRegex(extraArgs.condition)
|
|
|
|
|
: /^/;
|
|
|
|
|
const reflector = proxyApplyFn(propChain, function(...args) {
|
2024-02-02 18:46:59 +01:00
|
|
|
|
const arglist = args[args.length-1];
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
if ( Array.isArray(arglist) === false ) { return reflector(...args); }
|
|
|
|
|
const argBefore = arglist[argpos];
|
|
|
|
|
if ( reCondition.test(argBefore) === false ) { return reflector(...args); }
|
|
|
|
|
arglist[argpos] = normalValue;
|
2024-02-14 17:41:58 +01:00
|
|
|
|
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${normalValue}`);
|
Ensure scriptlet logging information make it to destination
Avoid race conditions between isolated world-side broadcast channel
and main-side broadcast channel, so as to not lose logging
information if the isolated world-side is not yet ready to
receive through its broadcast channel.
Additionally, added new scriptlet: `trusted-replace-argument`.
[...]##+js(trusted-replace-argument, fn, argpos, argval [,condition, pattern])
Where:
- `fn` is the function we want to proxy through an `apply` handler.
This can also be a class, in which case the scriptlet will proxy
through `construct` handler. At the moment, `fn` must exist at the
time the scriptlet executes.
- `argpos` is the 0-based position of the argument we want to change
- `argval` is the value we want to have for the argument -- the value
is interpreted the same way the value for `set-constant` is
interpreted.
- `condition, pattern` is a vararg which tells the scriptlet to act
only if `pattern` is found in the argument to overwrite.
Example of usage:
alliptvlinks.com##+js(trusted-replace-argument, MutationObserver, 0, noopFunc)
2024-01-26 18:18:30 +01:00
|
|
|
|
return reflector(...args);
|
2023-10-22 18:35:49 +02:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
2024-04-01 17:27:19 +02:00
|
|
|
|
|
|
|
|
|
builtinScriptlets.push({
|
|
|
|
|
name: 'trusted-replace-outbound-text.js',
|
|
|
|
|
requiresTrust: true,
|
|
|
|
|
fn: trustedReplaceOutboundText,
|
|
|
|
|
dependencies: [
|
|
|
|
|
'proxy-apply.fn',
|
|
|
|
|
'safe-self.fn',
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
function trustedReplaceOutboundText(
|
|
|
|
|
propChain = '',
|
|
|
|
|
pattern = '',
|
2024-04-02 17:04:27 +02:00
|
|
|
|
replacement = '',
|
|
|
|
|
...args
|
2024-04-01 17:27:19 +02:00
|
|
|
|
) {
|
|
|
|
|
if ( propChain === '' ) { return; }
|
|
|
|
|
const safe = safeSelf();
|
2024-04-02 17:04:27 +02:00
|
|
|
|
const logPrefix = safe.makeLogPrefix('trusted-replace-outbound-text', propChain, pattern, replacement, ...args);
|
2024-04-01 17:27:19 +02:00
|
|
|
|
const rePattern = safe.patternToRegex(pattern);
|
2024-04-02 17:04:27 +02:00
|
|
|
|
const extraArgs = safe.getExtraArgs(args);
|
|
|
|
|
const reCondition = safe.patternToRegex(extraArgs.condition || '');
|
2024-04-01 17:27:19 +02:00
|
|
|
|
const reflector = proxyApplyFn(propChain, function(...args) {
|
|
|
|
|
const textBefore = reflector(...args);
|
2024-04-02 17:04:27 +02:00
|
|
|
|
if ( pattern === '' ) {
|
|
|
|
|
safe.uboLog(logPrefix, 'Outbound text:\n', textBefore);
|
|
|
|
|
return textBefore;
|
2024-04-01 17:27:19 +02:00
|
|
|
|
}
|
2024-04-02 17:04:27 +02:00
|
|
|
|
reCondition.lastIndex = 0;
|
|
|
|
|
if ( reCondition.test(textBefore) === false ) { return textBefore; }
|
|
|
|
|
const textAfter = textBefore.replace(rePattern, replacement);
|
|
|
|
|
if ( textAfter === textBefore ) { return textBefore; }
|
|
|
|
|
safe.uboLog(logPrefix, 'Matched and replaced');
|
|
|
|
|
if ( safe.logLevel > 1 ) {
|
|
|
|
|
safe.uboLog(logPrefix, 'Modified outbound text:\n', textAfter);
|
2024-04-01 17:27:19 +02:00
|
|
|
|
}
|
|
|
|
|
return textAfter;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|