diff --git a/build.ts b/build.ts index a010a08..fbb87b7 100644 --- a/build.ts +++ b/build.ts @@ -10,51 +10,10 @@ const { builtinScriptlets: scriptlets } = await import( `https://raw.githubusercontent.com/gorhill/uBlock/${tagName}/src/js/resources/scriptlets.js` ); -const index = new Map(); -for (const scriptlet of scriptlets) { - index.set(scriptlet.name, scriptlet); - for (const name of scriptlet.aliases || []) { - index.set(name, scriptlet); - } -} - -// Each function once, by name, so compose() and run() can declare them in one shared scope. -const functions = new Map(); -const entryFunctions = []; - -const entries = scriptlets - .filter(scriptlet => scriptlet.name.endsWith('.js')) - .map((scriptlet) => { - const allDependencies = new Set(); - - const addDeps = (aScriptlet) => { - for (const dep of aScriptlet.dependencies || []) { - allDependencies.add(dep); - const bScriptlet = index.get(dep); - addDeps(bScriptlet); - } - }; - - addDeps(scriptlet); - - const deps = [...allDependencies].reverse().map((dep) => index.get(dep).fn); - - for (const fn of [...deps, scriptlet.fn]) { - functions.set(fn.name, fn.toString()); - } - entryFunctions.push(scriptlet.fn.name); - - return ` -scriptlets['${scriptlet.name}'] = { -aliases: ${JSON.stringify(scriptlet.aliases || [])}, -${scriptlet.world ? `world: '${scriptlet.world}',` : '' } -requiresTrust: ${scriptlet.requiresTrust || false}, -fn: '${scriptlet.fn.name}', -dependencies: ${JSON.stringify(deps.map((dep) => dep.name))}, -}; -`; - }) - .join('\n'); +// Every function once, by name; run() declares them all in one scope, so the scriptlets of an +// injection share one safeSelf() cache without touching globalThis, as in uBO. +const functions = new Map(scriptlets.map((scriptlet) => [scriptlet.fn.name, scriptlet.fn.toString()])); +const entries = scriptlets.filter((scriptlet) => scriptlet.name.endsWith('.js')); console.log(` /******************************************************************************* @@ -78,21 +37,28 @@ console.log(` Home: https://github.com/gorhill/uBlock */ -const functions = {}; -${[...functions].map(([name, source]) => `functions['${name}'] = ${source};`).join('\n')} - const scriptlets = {}; -${entries} +${entries + .map( + (scriptlet) => ` +scriptlets['${scriptlet.name}'] = { +aliases: ${JSON.stringify(scriptlet.aliases || [])}, +${scriptlet.world ? `world: '${scriptlet.world}',` : ''} +requiresTrust: ${scriptlet.requiresTrust || false}, +fn: '${scriptlet.fn.name}', +}; +`, + ) + .join('\n')} -// For chrome.scripting.executeScript(), which takes a function and JSON arguments but no code. +// The calls come last, so the class dependencies, which are not hoisted, are declared by then. export function run(scriptletGlobals, calls) { ${[...functions.values()].join('\n')} -const table = { ${entryFunctions.join(', ')} }; +const table = { ${entries.map((scriptlet) => scriptlet.fn.name).join(', ')} }; for (const [name, ...args] of calls) { try { table[name](...args); } catch {} } } -export { functions }; export default scriptlets; `); diff --git a/index.js b/index.js index 0798790..bc6d315 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import SCRIPTLETS, { functions, run } from './ubo.js'; +import SCRIPTLETS, { run } from './ubo.js'; const scriptlets = {}; @@ -9,26 +9,5 @@ for (const [name, scriptlet] of Object.entries(SCRIPTLETS)) { } } -// One scope for all calls of an injection, as in uBO: each dependency is declared once, so the -// scriptlets share one safeSelf() cache without touching globalThis. The calls come last, so the -// class dependencies, which are not hoisted, are declared by the time they run. -export function compose(calls, scriptletGlobals = {}) { - const declarations = new Map(); - const body = []; - for (const { scriptlet, args } of calls) { - for (const name of [...scriptlet.dependencies, scriptlet.fn]) { - declarations.set(name, functions[name].toString()); - } - body.push(`try { ${scriptlet.fn}(...${JSON.stringify(args)}); } catch {}`); - } - return [ - '(function () {', - `const scriptletGlobals = ${JSON.stringify(scriptletGlobals)};`, - ...declarations.values(), - ...body, - '})();', - ].join('\n'); -} - export { run }; export default scriptlets; diff --git a/test.js b/test.js index f8bba25..a0be436 100644 --- a/test.js +++ b/test.js @@ -1,8 +1,9 @@ import { test, suite } from "node:test"; import assert from "node:assert"; import vm from "node:vm"; -import scriptlets, { compose, run } from "./index.js"; -import { functions } from "./ubo.js"; +import scriptlets, { run } from "./index.js"; + +const runSource = run.toString(); test("default export is an object", () => { assert(typeof scriptlets === "object"); @@ -12,12 +13,8 @@ test("each scriptlet has basic properties", () => { for (const [name, scriptlet] of Object.entries(scriptlets)) { assert(name.length > 0, `${name} - name is too short`); assert( - functions[scriptlet.fn] instanceof Function, - `${name} - fn does not name a function` - ); - assert( - scriptlet.dependencies.every((dep) => functions[dep] instanceof Function), - `${name} - a dependency does not name a function` + runSource.includes(`function ${scriptlet.fn}(`), + `${name} - fn does not name a function of run()` ); assert( scriptlet.aliases instanceof Array, @@ -33,11 +30,16 @@ suite("uBO", () => { }); }); -suite("safeSelf() cache", () => { +suite("run()", () => { // Each hook clones the argument with safe.JSON_parse(safe.JSON_stringify(obj)) const scriptlet = scriptlets["trusted-edit-inbound-object.js"]; const HOOKS = 6; - const hookArgs = (i) => ["JSON.stringify", "0", `[?.hook${i}]+={"edited${i}":true}`]; + const calls = Array.from({ length: HOOKS }, (_, i) => [ + scriptlet.fn, + "JSON.stringify", + "0", + `[?.hook${i}]+={"edited${i}":true}`, + ]); // A fresh realm is the page; counters wrap the native JSON methods before the scriptlets run function createPage() { @@ -54,7 +56,15 @@ suite("safeSelf() cache", () => { return page; } - function assertLinear(page) { + test("declares safeSelf() once", () => { + assert.strictEqual(runSource.match(/^function safeSelf\(/gm).length, 1); + }); + + test("shares one safeSelf() cache, so stacked hooks stay linear", () => { + const page = createPage(); + // Same call as chrome.scripting.executeScript() makes: the function source with JSON arguments + vm.runInContext(`(${runSource})(...${JSON.stringify([{}, calls])});`, page); + const result = vm.runInContext( "calls.parse = 0; calls.stringify = 0; JSON.stringify({ hook0: true, hook5: true });", page @@ -62,34 +72,15 @@ suite("safeSelf() cache", () => { assert.deepStrictEqual(JSON.parse(result), { hook0: true, hook5: true, edited0: true, edited5: true }); // One call from the page plus one clone per hook, not 2^n assert.deepStrictEqual({ ...page.calls }, { parse: HOOKS, stringify: HOOKS + 1 }); - } + }); + + test("leaves no global behind", () => { + const page = createPage(); + vm.runInContext(`(${runSource})(...${JSON.stringify([{}, calls])});`, page); - function assertNoGlobal(page) { const keys = vm .runInContext("Object.getOwnPropertySymbols(globalThis)", page) .filter((key) => key.description.startsWith("safeSelf.")); assert.strictEqual(keys.length, 0); - } - - test("compose() shares it in one scope, without a global", () => { - const page = createPage(); - const code = compose( - Array.from({ length: HOOKS }, (_, i) => ({ scriptlet, args: hookArgs(i) })) - ); - assert.strictEqual(code.match(/^function safeSelf\(/gm).length, 1); - vm.runInContext(code, page); - - assertLinear(page); - assertNoGlobal(page); - }); - - test("run() shares it in one scope, without a global", () => { - const page = createPage(); - // Same call as chrome.scripting.executeScript() makes: the function source with JSON arguments - const calls = Array.from({ length: HOOKS }, (_, i) => [scriptlet.fn, ...hookArgs(i)]); - vm.runInContext(`(${run})(...${JSON.stringify([{}, calls])});`, page); - - assertLinear(page); - assertNoGlobal(page); }); }); diff --git a/ubo.js b/ubo.js index 8369289..fd9ad99 100644 --- a/ubo.js +++ b/ubo.js @@ -20,6140 +20,6 @@ Home: https://github.com/gorhill/uBlock */ -const functions = {}; -functions['runAtHtmlElementFn'] = function runAtHtmlElementFn(fn) { - if ( document.documentElement ) { - fn(); - return; - } - const observer = new MutationObserver(( ) => { - observer.disconnect(); - fn(); - }); - observer.observe(document, { childList: true }); -}; -functions['trapPropertyFn'] = function trapPropertyFn(propChain, handler, options = {}) { - if ( propChain === '' ) { return; } - let owner = self; - let prop = propChain; - 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); - } - const safe = safeSelf(); - if ( trapPropertyFn.db === undefined ) { - trapPropertyFn.db = new WeakMap(); - trapPropertyFn.entryFromContext = (owner, prop) => { - const handlers = trapPropertyFn.db.get(owner); - return handlers?.get(prop); - }; - trapPropertyFn.getter = (owner, prop) => { - const entry = trapPropertyFn.entryFromContext(owner, prop); - if ( entry === undefined ) { return; } - let r = entry.value; - for ( const desc of entry.stack ) { - try { r = desc.get(); } catch (e) { - if ( entry.canThrow ) { throw e; } - } - } - return r; - }; - trapPropertyFn.setter = (owner, prop, value) => { - const entry = trapPropertyFn.entryFromContext(owner, prop); - if ( entry === undefined ) { return; } - entry.value = value; - for ( const desc of entry.stack ) { - try { desc.set(value); } catch (e) { - if ( entry.canThrow ) { throw e; } - } - } - }; - } - const { db } = trapPropertyFn; - const handlers = db.get(owner) || new Map(); - if ( handlers.size === 0 ) { - db.set(owner, handlers); - } - const entry = handlers.get(prop) || { - value: owner[prop], - stack: [], - }; - entry.stack.push(handler); - if ( entry.stack.length > 1 ) { return entry.value; } - Object.assign(entry, options); - handlers.set(prop, entry); - const desc = safe.Object_getOwnPropertyDescriptor(owner, prop); - if ( desc instanceof safe.Object ) { - if ( desc.get || desc.set ) { - entry.stack.push(desc); - } - } - try { - safe.Object_defineProperty(owner, prop, { - get() { - return trapPropertyFn.getter(owner, prop); - }, - set(value) { - trapPropertyFn.setter(owner, prop, value); - } - }); - } catch { - } - return entry.value; -}; -functions['safeSelf'] = function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; - } - const self = globalThis; - const safe = { - 'Array_from': Array.from, - 'Error': self.Error, - 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), - 'Math_floor': Math.floor, - 'Math_max': Math.max, - 'Math_min': Math.min, - 'Math_random': Math.random, - 'Object': Object, - 'Object_defineProperty': Object.defineProperty.bind(Object), - 'Object_defineProperties': Object.defineProperties.bind(Object), - 'Object_fromEntries': Object.fromEntries.bind(Object), - 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), - 'Object_hasOwn': Object.hasOwn.bind(Object), - 'Object_toString': Object.prototype.toString, - 'RegExp': self.RegExp, - 'RegExp_test': Function.prototype.call.bind(self.RegExp.prototype.test), - 'RegExp_exec': self.RegExp.prototype.exec, - 'Request_clone': self.Request.prototype.clone, - 'String': self.String, - 'String_fromCharCode': String.fromCharCode, - 'String_split': String.prototype.split, - 'XMLHttpRequest': self.XMLHttpRequest, - 'addEventListener': self.EventTarget.prototype.addEventListener, - 'removeEventListener': self.EventTarget.prototype.removeEventListener, - 'fetch': self.fetch, - 'JSON': self.JSON, - 'JSON_parse': Function.prototype.call.bind(self.JSON.parse, self.JSON), - 'JSON_stringify': Function.prototype.call.bind(self.JSON.stringify, self.JSON), - 'log': console.log.bind(console), - // Properties - logLevel: 0, - // Methods - makeLogPrefix(...args) { - return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; - }, - uboLog(...args) { - 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); - }, - escapeRegexChars(s) { - return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - }, - initPattern(pattern, options = {}) { - if ( pattern === '' ) { - return { matchAll: true, expect: true }; - } - const expect = (options.canNegate !== true || pattern.startsWith('!') === false); - 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, - }; - } - if ( options.flags !== undefined ) { - return { - re: new this.RegExp(this.escapeRegexChars(pattern), - options.flags - ), - expect, - }; - } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; - } - return haystack.includes(details.pattern) === details.expect; - }, - patternToRegex(pattern, flags = undefined, verbatim = false) { - if ( pattern === '' ) { return /^/; } - const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); - if ( match === null ) { - const reStr = this.escapeRegexChars(pattern); - return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); - } - try { - return new RegExp(match[1], match[2] || undefined); - } - catch { - } - return /^/; - }, - parseVarargs(varargs) { - const entries = varargs.reduce((out, v, i, a) => { - if ( i & 1 ) { return out; } - const rawValue = a[i+1]; - const value = /^\d+$/.test(rawValue) - ? parseInt(rawValue, 10) - : rawValue; - out.push([ a[i], value ]); - return out; - }, []); - return this.Object_fromEntries(entries); - }, - }; - safeSelf.safe = safe; - if ( scriptletGlobals.bcSecret === undefined ) { return safe; } - // This is executed only when the logger is opened - safe.logLevel = scriptletGlobals.logLevel || 1; - let lastLogType = ''; - let lastLogText = ''; - let lastLogTime = 0; - safe.toLogText = (type, ...args) => { - if ( args.length === 0 ) { return; } - const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; - if ( text === lastLogText && type === lastLogType ) { - if ( (Date.now() - lastLogTime) < 5000 ) { return; } - } - lastLogType = type; - lastLogText = text; - lastLogTime = Date.now(); - return text; - }; - try { - const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); - let bcBuffer = []; - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - 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?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; -}; -functions['getRandomTokenFn'] = function getRandomTokenFn() { - const safe = safeSelf(); - return safe.String_fromCharCode(Date.now() % 26 + 97) + - safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); -}; -functions['getExceptionTokenFn'] = function getExceptionTokenFn() { - const token = getRandomTokenFn(); - 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; -}; -functions['abortCurrentScriptFn'] = function abortCurrentScriptFn( - target = '', - needle = '', - context = '' -) { - if ( typeof target !== 'string' ) { return; } - if ( target === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('abort-current-script', target, needle, context); - const reNeedle = safe.patternToRegex(needle); - const reContext = safe.patternToRegex(context); - const thisScript = document.currentScript; - const exceptionToken = getExceptionTokenFn(); - const scriptTexts = new WeakMap(); - const textContentGetter = Object.getOwnPropertyDescriptor(Node.prototype, 'textContent').get; - const getScriptText = elem => { - let text = textContentGetter.call(elem); - 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 { - } - scriptTexts.set(elem, text); - return text; - }; - const validate = ( ) => { - const e = document.currentScript; - if ( e instanceof HTMLScriptElement === false ) { return; } - if ( e === thisScript ) { return; } - if ( context !== '' && reContext.test(e.src) === false ) { return; } - if ( safe.logLevel > 1 && context !== '' ) { - safe.uboLog(logPrefix, `Matched src\n${e.src}`); - } - const scriptText = getScriptText(e); - if ( reNeedle.test(scriptText) === false ) { return; } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched text\n${scriptText}`); - } - safe.uboLog(logPrefix, 'Aborted'); - throw new ReferenceError(exceptionToken); - }; - let currentValue = trapPropertyFn(target, { - get: function() { - validate(); - return currentValue; - }, - set: function(a) { - validate(); - currentValue = a; - } - }, { canThrow: true }); -}; -functions['abortCurrentScript'] = function abortCurrentScript(...args) { - runAtHtmlElementFn(( ) => { - abortCurrentScriptFn(...args); - }); -}; -functions['runAt'] = function runAt(fn, when) { - const intFromReadyState = state => { - const targets = { - 'loading': 1, 'asap': 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 ( Object.hasOwn(targets, prop) === false ) { continue; } - return targets[prop]; - } - return 0; - }; - const runAt = intFromReadyState(when); - if ( intFromReadyState(document.readyState) >= runAt ) { - fn(); return; - } - const onStateChange = ( ) => { - if ( intFromReadyState(document.readyState) < runAt ) { return; } - fn(); - safe.removeEventListener.apply(document, args); - }; - const safe = safeSelf(); - const args = [ 'readystatechange', onStateChange, { capture: true } ]; - safe.addEventListener.apply(document, args); -}; -functions['setAttrFn'] = function setAttrFn( - trusted = false, - logPrefix, - selector = '', - attr = '', - value = '', - options = {} -) { - if ( selector === '' ) { return; } - if ( attr === '' ) { return; } - - const safe = safeSelf(); - const copyFrom = trusted === false && /^\[.+\]$/.test(value) - ? value.slice(1, -1) - : ''; - - const extractValue = elem => copyFrom !== '' - ? elem.getAttribute(copyFrom) || '' - : value; - - const applySetAttr = ( ) => { - let elems; - try { - elems = document.querySelectorAll(selector); - } catch { - return false; - } - for ( const elem of elems ) { - const before = elem.getAttribute(attr); - const after = extractValue(elem); - if ( after === before ) { continue; } - if ( after !== '' && /^on/i.test(attr) ) { - if ( attr.toLowerCase() in elem ) { continue; } - } - elem.setAttribute(attr, after); - safe.uboLog(logPrefix, `${attr}="${after}"`); - } - 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); - const root = document.documentElement; - if ( root instanceof self.Node === false ) { return; } - observer.observe(root, { subtree: true, childList: true }); - }; - runAt(( ) => { start(); }, options.runAt || 'idle'); -}; -functions['setAttr'] = function setAttr( - selector = '', - attr = '', - value = '', - ...varargs -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('set-attr', selector, attr, value); - const validValues = [ '', 'false', 'true' ]; - if ( validValues.includes(value.toLowerCase()) === false ) { - if ( /^\d+$/.test(value) ) { - const n = parseInt(value, 10); - if ( n >= 32768 ) { return; } - value = `${n}`; - } else if ( /^\[.+\]$/.test(value) === false ) { - return; - } - } - const options = safe.parseVarargs(varargs); - setAttrFn(false, logPrefix, selector, attr, value, options); -}; -functions['trustedSetAttr'] = function trustedSetAttr( - selector = '', - attr = '', - value = '', - ...varargs -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-set-attr', selector, attr, value); - const options = safe.parseVarargs(varargs); - setAttrFn(true, logPrefix, selector, attr, value, options); -}; -functions['onIdleFn'] = function onIdleFn(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); -}; -functions['offIdleFn'] = function offIdleFn(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); -}; -functions['removeAttr'] = function removeAttr( - rawToken = '', - rawSelector = '', - behavior = '', - ...varargs -) { - if ( typeof rawToken !== 'string' ) { return; } - if ( rawToken === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('remove-attr', - rawToken, rawSelector, behavior, ...varargs - ); - const tokens = safe.String_split.call(rawToken, /\s*\|\s*/); - const selector = tokens.map(a => { - const b = CSS.escape(a); - return rawSelector.includes(`[${b}]`) ? rawSelector : `${rawSelector}[${b}]`; - }).join(','); - const lazily = /\basap\b/.test(behavior) === false; - const options = safe.parseVarargs(varargs); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Target selector:\n\t${selector}`); - } - const rmattrFromNode = node => { - for ( const attr of tokens ) { - if ( node.hasAttribute(attr) === false ) { continue; } - node.removeAttribute(attr); - safe.uboLog(logPrefix, `Removed attribute '${attr}'`); - } - }; - const rmattr = nodes => { - for ( const node of nodes ?? document.querySelectorAll(selector) ) { - rmattrFromNode(node); - } - }; - const rmAttrLazily = ( ) => { - if ( rmAttrLazily.timer !== undefined ) { return; } - rmAttrLazily.timer = onIdleFn(( ) => { - rmAttrLazily.timer = undefined; - rmattr(); - }, { timeout: 17 }); - }; - const mutationHandler = mutations => { - for ( const { addedNodes, removedNodes } of mutations ) { - for ( const node of addedNodes ) { - if ( node.nodeType !== 1 ) { continue; } - if ( lazily ) { return rmAttrLazily(); } - if ( node.matches(selector) ) { - rmattrFromNode(node); - } - if ( node.childElementCount ) { - rmattr(node.querySelectorAll(selector)); - } - } - if ( lazily ) { return; } - for ( const node of removedNodes ) { - if ( node.nodeType !== 1 ) { continue; } - if ( node.matches(selector) ) { - rmattrFromNode(node); - } - } - } - }; - const stop = ( ) => { - if ( start.observer ) { - start.observer.disconnect(); - start.observer = undefined; - } - if ( rmAttrLazily.timer ) { - offIdleFn(rmAttrLazily.timer); - rmAttrLazily.timer = undefined; - } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, 'Quitting'); - } - }; - const start = ( ) => { - rmattr(); - if ( /\bstay\b/.test(behavior) === false ) { - if ( options.quitAfter === undefined ) { return; } - } - start.observer = new MutationObserver(mutationHandler); - start.observer.observe(document, { - attributes: true, - attributeFilter: tokens, - childList: true, - subtree: true, - }); - if ( options.quitAfter ) { - runAt(( ) => { - self.setTimeout(stop, options.quitAfter * 1000); - }, 'load'); - } - }; - runAt(( ) => { start(); }, safe.String_split.call(behavior, /\s+/)); -}; -functions['trustedCreateHTML'] = function trustedCreateHTML( - parentSelector, - htmlStr = '', - durationStr = '', - ...varargs -) { - if ( parentSelector === '' ) { return; } - if ( htmlStr === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-create-html', parentSelector, htmlStr, durationStr); - const extraArgs = safe.parseVarargs(varargs); - // We do not want to recursively create elements - self.trustedCreateHTML = true; - let ancestor = self.frameElement; - while ( ancestor ) { - const doc = ancestor.ownerDocument; - if ( doc === null ) { break; } - const win = doc.defaultView; - if ( win === null ) { break; } - if ( win.trustedCreateHTML ) { return; } - ancestor = ancestor.frameElement; - } - const duration = parseInt(durationStr, 10); - const domParser = new DOMParser(); - const externalDoc = domParser.parseFromString(htmlStr, 'text/html'); - const toAppend = []; - while ( externalDoc.body.firstChild !== null ) { - toAppend.push(document.adoptNode(externalDoc.body.firstChild)); - } - if ( toAppend.length === 0 ) { return; } - const toRemove = []; - const remove = ( ) => { - for ( const node of toRemove ) { - if ( node.parentNode === null ) { continue; } - node.parentNode.removeChild(node); - } - safe.uboLog(logPrefix, 'Node(s) removed'); - }; - const appendOne = (target, nodes) => { - for ( const node of nodes ) { - target.append(node); - if ( isNaN(duration) ) { continue; } - toRemove.push(node); - } - }; - const append = ( ) => { - const targets = document.querySelectorAll(parentSelector); - if ( targets.length === 0 ) { return false; } - const limit = Math.min(targets.length, extraArgs.limit || 1) - 1; - for ( let i = 0; i < limit; i++ ) { - appendOne(targets[i], toAppend.map(a => a.cloneNode(true))); - } - appendOne(targets[limit], toAppend); - safe.uboLog(logPrefix, 'Node(s) appended'); - if ( toRemove.length === 0 ) { return true; } - setTimeout(remove, duration); - return true; - }; - const start = ( ) => { - if ( append() ) { return; } - const observer = new MutationObserver(( ) => { - if ( append() === false ) { return; } - observer.disconnect(); - }); - const observerOptions = { - childList: true, - subtree: true, - }; - if ( /[#.[]/.test(parentSelector) ) { - observerOptions.attributes = true; - if ( parentSelector.includes('[') === false ) { - observerOptions.attributeFilter = []; - if ( parentSelector.includes('#') ) { - observerOptions.attributeFilter.push('id'); - } - if ( parentSelector.includes('.') ) { - observerOptions.attributeFilter.push('class'); - } - } - } - observer.observe(document, observerOptions); - }; - runAt(start, extraArgs.runAt || 'loading'); -}; -functions['urlSkip'] = function urlSkip(url, blocked, steps) { - try { - let redirectBlocked = false; - let urlout = url; - for ( const step of steps ) { - const urlin = urlout; - const c0 = step.charCodeAt(0); - // Extract from hash - if ( c0 === 0x23 && step === '#' ) { // # - const pos = urlin.indexOf('#'); - urlout = pos !== -1 ? urlin.slice(pos+1) : ''; - continue; - } - // Extract from URL parameter name at position i - if ( c0 === 0x26 ) { // & - const i = (parseInt(step.slice(1)) || 0) - 1; - if ( i < 0 ) { return; } - const url = new URL(urlin); - if ( i >= url.searchParams.size ) { return; } - const params = Array.from(url.searchParams.keys()); - urlout = decodeURIComponent(params[i]); - continue; - } - // Enforce https - if ( c0 === 0x2B && step === '+https' ) { // + - const s = urlin.replace(/^https?:\/\//, ''); - if ( /^[\w-]:\/\//.test(s) ) { return; } - urlout = `https://${s}`; - continue; - } - // Decode - if ( c0 === 0x2D ) { // - - // Base64 - if ( step === '-base64' ) { - urlout = self.atob(urlin); - continue; - } - // Safe Base64 - if ( step === '-safebase64' ) { - if ( urlSkip.safeBase64Replacer === undefined ) { - urlSkip.safeBase64Map = { '-': '+', '_': '/' }; - urlSkip.safeBase64Replacer = s => urlSkip.safeBase64Map[s]; - } - urlout = urlin.replace(/[-_]/g, urlSkip.safeBase64Replacer); - urlout = self.atob(urlout); - continue; - } - // URI component - if ( step === '-uricomponent' ) { - urlout = decodeURIComponent(urlin); - continue; - } - // Enable skip of blocked requests - if ( step === '-blocked' ) { - redirectBlocked = true; - continue; - } - } - // Regex extraction from first capture group - if ( c0 === 0x2F ) { // / - const re = new RegExp(step.slice(1, -1)); - const match = re.exec(urlin); - if ( match === null ) { return; } - if ( match.length <= 1 ) { return; } - urlout = match[1]; - continue; - } - // Extract from URL parameter - if ( c0 === 0x3F ) { // ? - urlout = (new URL(urlin)).searchParams.get(step.slice(1)); - if ( urlout === null ) { return; } - if ( urlout.includes(' ') ) { - urlout = urlout.replace(/ /g, '%20'); - } - continue; - } - // Unknown directive - return; - } - const urlfinal = new URL(urlout); - if ( urlfinal.protocol !== 'https:' ) { - if ( urlfinal.protocol !== 'http:' ) { return; } - } - if ( blocked && redirectBlocked !== true ) { return; } - return urlout; - } catch { - } -}; -functions['hrefSanitizer'] = function hrefSanitizer( - selector = '', - source = '' -) { - if ( typeof selector !== 'string' ) { return; } - if ( selector === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('href-sanitizer', selector, source); - if ( source === '' ) { source = 'text'; } - const sanitizeCopycats = (href, text) => { - let elems = []; - try { - elems = document.querySelectorAll(`a[href="${href}"`); - } - catch { - } - for ( const elem of elems ) { - elem.setAttribute('href', text); - } - return elems.length; - }; - const validateURL = text => { - if ( typeof text !== 'string' ) { return ''; } - if ( text === '' ) { return ''; } - if ( /[\x00-\x20\x7f]/.test(text) ) { return ''; } - try { - const url = new URL(text, document.location); - return url.href; - } catch { - } - return ''; - }; - const extractURL = (elem, source) => { - if ( /^\[.*\]$/.test(source) ) { - return elem.getAttribute(source.slice(1,-1).trim()) || ''; - } - if ( source === 'text' ) { - return elem.textContent - .replace(/^[^\x21-\x7e]+/, '') // remove leading invalid characters - .replace(/[^\x21-\x7e]+$/, ''); // remove trailing invalid characters - } - const steps = source.replace(/(\S)\?/g, '\\1 ?').split(/\s+/); - const url = urlSkip(elem.href, false, steps); - if ( url === undefined ) { return; } - return url.replace(/ /g, '%20'); - }; - const sanitize = ( ) => { - let elems = []; - try { - elems = document.querySelectorAll(selector); - } - catch { - return false; - } - for ( const elem of elems ) { - if ( elem.localName !== 'a' ) { continue; } - if ( elem.hasAttribute('href') === false ) { continue; } - const href = elem.getAttribute('href'); - const text = extractURL(elem, source); - const hrefAfter = validateURL(text); - if ( hrefAfter === '' ) { continue; } - if ( hrefAfter === href ) { continue; } - elem.setAttribute('href', hrefAfter); - const count = sanitizeCopycats(href, hrefAfter); - safe.uboLog(logPrefix, `Sanitized ${count+1} links to\n${hrefAfter}`); - } - 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; } - timer = onIdleFn(( ) => { - timer = undefined; - sanitize(); - }); - }; - const start = ( ) => { - if ( sanitize() === false ) { return; } - observer = new MutationObserver(onDomChanged); - observer.observe(document.body, { - subtree: true, - childList: true, - }); - }; - runAt(( ) => { start(); }, 'interactive'); -}; -functions['proxyApplyConfig'] = function proxyApplyConfig(config = '') { - try { - if ( typeof proxyApplyFn !== 'function' ) { return; } - config = JSON.parse(config); - if ( typeof config !== 'object' ) { return; } - Object.assign(proxyApplyFn, config); - } catch { - } -}; -functions['proxyApplyFn'] = function proxyApplyFn( - target = '', - handler = '', - options = {} -) { - 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 ( proxyApplyFn.CtorContext === undefined ) { - proxyApplyFn.ctorContexts = []; - proxyApplyFn.CtorContext = class { - constructor(...args) { - this.init(...args); - } - init(callFn, callArgs) { - this.callFn = callFn; - this.callArgs = callArgs; - return this; - } - reflect() { - const r = Reflect.construct(this.callFn, this.callArgs); - this.callFn = this.callArgs = this.private = undefined; - proxyApplyFn.ctorContexts.push(this); - return r; - } - static factory(...args) { - return proxyApplyFn.ctorContexts.length !== 0 - ? proxyApplyFn.ctorContexts.pop().init(...args) - : new proxyApplyFn.CtorContext(...args); - } - }; - proxyApplyFn.applyContexts = []; - proxyApplyFn.ApplyContext = class { - constructor(...args) { - this.init(...args); - } - init(callFn, thisArg, callArgs) { - this.callFn = callFn; - this.thisArg = thisArg; - this.callArgs = callArgs; - return this; - } - reflect() { - const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs); - this.callFn = this.thisArg = this.callArgs = this.private = undefined; - proxyApplyFn.applyContexts.push(this); - return r; - } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); - } - }; - proxyApplyFn.isCtor = new Map(); - proxyApplyFn.proxies = new WeakMap(); - if ( (options.skipToString || proxyApplyFn.skipToString) !== true ) { - proxyApplyFn.nativeToString = Function.prototype.toString; - const proxiedToString = new Proxy(Function.prototype.toString, { - apply(target, thisArg) { - let proxied = thisArg; - for(;;) { - const fn = proxyApplyFn.proxies.get(proxied); - if ( fn === undefined ) { break; } - proxied = fn; - } - return proxyApplyFn.nativeToString.call(proxied); - } - }); - proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); - Function.prototype.toString = proxiedToString; - } - } - if ( proxyApplyFn.isCtor.has(target) === false ) { - proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); - } - const proxyDetails = { - apply(target, thisArg, args) { - return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); - } - }; - if ( proxyApplyFn.isCtor.get(target) ) { - proxyDetails.construct = function(target, args) { - return handler(proxyApplyFn.CtorContext.factory(target, args)); - }; - } - const proxiedTarget = new Proxy(fn, proxyDetails); - proxyApplyFn.proxies.set(proxiedTarget, fn); - context[prop] = proxiedTarget; -}; -functions['JSONPath'] = class JSONPath { - static create(query) { - const jsonp = new JSONPath(); - jsonp.compile(query); - return jsonp; - } - static toJSON(obj, stringifier, ...args) { - return (stringifier || JSON.stringify)(obj, ...args) - .replace(/\//g, '\\/'); - } - static keys = Object.keys; - static entries = Object.entries; - static hasOwn = Object.hasOwn; - static Regex = RegExp; - get value() { - return this.#compiled && this.#compiled.rval; - } - set value(v) { - if ( this.#compiled === undefined ) { return; } - this.#compiled.rval = v; - } - get valid() { - return this.#compiled !== undefined; - } - compile(query) { - this.#compiled = undefined; - this.v2 = query.startsWith('v2:'); - if ( this.v2 ) { query = query.slice(3); } - const r = this.#compile(query, 0); - if ( r === undefined ) { return; } - if ( r.i !== query.length ) { - let val; - if ( query.startsWith('=', r.i) ) { - const match = this.#reRval.exec(query.slice(r.i)); - if ( match ) { - r.modify = match[1]; - val = match[2]; - } else { - val = query.slice(r.i+1); - } - } else if ( query.startsWith('+=', r.i) ) { - r.modify = '+'; - val = query.slice(r.i+2); - } - try { r.rval = JSON.parse(val); } - catch { return; } - } - r.v2 = this.v2; - this.#compiled = r; - } - evaluate(root) { - if ( this.valid === false ) { return []; } - this.#root = { '$': root }; - const paths = this.#evaluate(this.#compiled.steps, []); - this.#root = null; - return paths; - } - apply(root) { - if ( this.valid === false ) { return; } - const { rval } = this.#compiled; - this.#root = { '$': root }; - const paths = this.#evaluate(this.#compiled.steps, []); - let i = paths.length - if ( i === 0 ) { this.#root = null; return; } - while ( i-- ) { - const { obj, key } = this.#resolvePath(paths[i]); - if ( obj === undefined ) { continue; } - if ( rval !== undefined ) { - this.#modifyVal(obj, key); - } else if ( Array.isArray(obj) && typeof key === 'number' ) { - obj.splice(key, 1); - } else { - delete obj[key]; - } - } - const result = this.#root['$'] ?? null; - this.#root = null; - return result; - } - dump() { - return JSON.stringify(this.#compiled); - } - toJSON(obj, ...args) { - return JSONPath.toJSON(obj, null, ...args) - } - get [Symbol.toStringTag]() { - return 'JSONPath'; - } - #UNDEFINED = 0; - #ROOT = 1; - #CURRENT = 2; - #CHILDREN = 3; - #DESCENDANTS = 4; - #QUANTIFIER = 5; - #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; - #reIndice = /^-?\d+/; - #reRval = /^=([a-z]+)\((.+)\)$/; - #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; - #root; - #compiled; - #compile(query, i) { - if ( query.length === 0 ) { return; } - const steps = []; - let c = query.charCodeAt(i); - if ( c === 0x24 /* $ */ ) { - steps.push({ mv: this.#ROOT }); - i += 1; - } else if ( c === 0x40 /* @ */ ) { - steps.push({ mv: this.#CURRENT }); - i += 1; - } else { - steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); - } - let mv = this.#UNDEFINED; - for (;;) { - if ( i === query.length ) { break; } - c = query.charCodeAt(i); - if ( c === 0x20 /* whitespace */ ) { - i += 1; - continue; - } - // Dot accessor syntax - if ( c === 0x2E /* . */ ) { - if ( mv !== this.#UNDEFINED ) { return; } - if ( query.startsWith('..', i) ) { - mv = this.#DESCENDANTS; - i += 2; - } else { - mv = this.#CHILDREN; - i += 1; - } - continue; - } - if ( c === 0x3B /* ; */ ) { - if ( query.startsWith(';$', i) === false ) { return; } - steps.push( - { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, - { mv: this.#ROOT } - ); - i += 2; - mv = this.#UNDEFINED; - continue; - } - if ( c === 0x7B /* { */ ) { - const match = this.#reQuantifier.exec(query.slice(i)); - if ( match === null ) { return; } - const comma = match[1].indexOf(','); - let min, max; - if ( comma === -1 ) { - min = max = parseInt(match[1]); - } else { - min = parseInt(match[1].slice(0, comma)) || 0; - max = parseInt(match[1].slice(comma+1)) || 1e6; - } - steps.push( - { mv: this.#QUANTIFIER, min, max }, - { mv: this.#ROOT } - ); - i += match[0].length; - mv = this.#UNDEFINED; - continue; - } - if ( c !== 0x5B /* [ */ ) { - if ( mv === this.#UNDEFINED ) { - const step = steps.at(-1); - if ( step === undefined ) { return; } - const j = this.#compileExpr(query, step, i); - if ( j ) { i = j; } - break; - } - const r = this.#consumeUnquotedIdentifier(query, i); - if ( r === undefined ) { return; } - steps.push({ mv, k: r.s }); - i = r.i; - mv = this.#UNDEFINED; - continue; - } - // Bracket accessor syntax - if ( mv === this.#CHILDREN ) { return; } - if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; - const j = i + 2 + not; - const r = this.#compile(query, j); - if ( r === undefined ) { return; } - if ( query.startsWith(']', r.i) === false ) { return; } - if ( not ) { r.steps.at(-1).not = true; } - steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); - i = r.i + 1; - mv = this.#UNDEFINED; - continue; - } - if ( query.startsWith('[*]', i) ) { - mv ||= this.#CHILDREN; - steps.push({ mv, k: '*' }); - i += 3; - mv = this.#UNDEFINED; - continue; - } - const r = this.#consumeIdentifier(query, i+1); - if ( r === undefined ) { return; } - mv ||= this.#CHILDREN; - steps.push({ mv, k: r.s }); - i = r.i + 1; - mv = this.#UNDEFINED; - } - if ( steps.length === 0 ) { return; } - if ( mv !== this.#UNDEFINED ) { return; } - return { steps, i }; - } - #evaluate(steps, pathin) { - let resultset = []; - if ( Array.isArray(steps) === false ) { return resultset; } - for ( const step of steps ) { - switch ( step.mv ) { - case this.#ROOT: - resultset = [ [ '$' ] ]; - break; - case this.#CURRENT: - if ( step.op ) { - const { obj, key } = this.#resolvePath(pathin); - if ( obj === undefined ) { return []; } - const outcome = this.#evaluateExpr(step, obj, key); - if ( outcome !== true ) { break; } - } - resultset = [ pathin ]; - break; - case this.#CHILDREN: - case this.#DESCENDANTS: { - if ( resultset.length === 0 ) { break; } - resultset = this.#getMatches(resultset, step); - break; - } - case this.#QUANTIFIER: { - const { length } = resultset; - if ( length < step.min || length > step.max ) { return []; } - resultset = []; - break; - } - default: - break; - } - } - return resultset; - } - #getMatches(listin, step) { - const listout = []; - for ( const pathin of listin ) { - const { value: owner } = this.#resolvePath(pathin); - if ( owner === undefined ) { continue; } - if ( step.steps ) { - this.#getMatchesFromExpr(pathin, step, owner, listout); - continue; - } - const iter = this.#expandKey(owner, step.k); - if ( iter ) { - for ( const k of iter ) { - const outcome = this.#evaluateExpr(step, owner, k); - if ( outcome !== true ) { continue; } - listout.push([ ...pathin, k ]); - } - } - if ( step.mv !== this.#DESCENDANTS ) { continue; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - const iter = this.#expandKey(obj[key], step.k); - if ( iter === undefined ) { continue; } - for ( const k of iter ) { - const outcome = this.#evaluateExpr(step, obj[key], k); - if ( outcome !== true ) { continue; } - listout.push([ ...pathin, ...path, k ]); - } - } - } - return listout; - } - #expandKey(owner, k) { - if ( typeof owner !== 'object' || owner === null ) { return; } - if ( Array.isArray(k) ) { - const out = []; - for ( const a of k ) { - const iter = this.#expandKey(owner, a); - if ( iter === undefined ) { continue; } - out.push(...iter); - } - return out; - } - if ( typeof k === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - return [ k >= 0 ? k : owner.length + k ]; - } - if ( k === '*' ) { - if ( Array.isArray(owner) ) { return owner.keys(); } - return JSONPath.keys(owner); - } - if ( k instanceof JSONPath.Regex ) { - const out = []; - for ( const key of JSONPath.keys(owner) ) { - if ( k.test(key) === false ) { continue; } - out.push(key); - } - return out; - } - return [ k ]; - } - #getMatchesFromExpr(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); - for ( const { path } of this.#getDescendants(owner, recursive) ) { - const q = v2 ? [ ...pathin, ...path ] : pathin; - const r = this.#evaluate(step.steps, q); - if ( Boolean(r?.length) === false ) { continue; } - out.push(q); - if ( v2 === false ) { break; } - } - } - #getDescendants(v, recursive) { - const iterator = { - next() { - const n = this.stack.length; - if ( n === 0 ) { - this.value = undefined; - this.done = true; - return this; - } - const details = this.stack[n-1]; - const entry = details.keys.next(); - if ( entry.done ) { - this.stack.pop(); - this.path.pop(); - return this.next(); - } - this.path[n-1] = entry.value; - this.value = { - obj: details.obj, - key: entry.value, - path: this.path.slice(), - }; - const v = this.value.obj[this.value.key]; - if ( recursive ) { - if ( Array.isArray(v) ) { - this.stack.push({ obj: v, keys: v.keys() }); - } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); - } - } - return this; - }, - path: [], - value: undefined, - done: false, - stack: [], - [Symbol.iterator]() { return this; }, - }; - if ( Array.isArray(v) ) { - iterator.stack.push({ obj: v, keys: v.keys() }); - } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); - } - return iterator; - } - #consumeIdentifier(query, i) { - const keys = []; - let needIdentifier = true; - while ( i < query.length ) { - const c0 = query.charCodeAt(i); - if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x20 /* SPACE */ ) { - i += 1; - continue; - } - if ( c0 === 0x2C /* , */ ) { - if ( needIdentifier ) { return; } - i += 1; - needIdentifier = true; - continue; - } - if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, c0, i+1); - if ( r === undefined ) { return; } - keys.push(r.s); - i = r.i; - needIdentifier = false; - continue; - } - if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { - const match = this.#reIndice.exec(query.slice(i)); - if ( match === null ) { return; } - const indice = parseInt(query.slice(i), 10); - keys.push(indice); - i += match[0].length; - needIdentifier = false; - continue; - } - if ( this.v2 ) { return; } - const r = this.#consumeUnquotedIdentifier(query, i); - if ( r === undefined ) { return; } - keys.push(r.s); - i = r.i; - } - if ( needIdentifier ) { return; } - return { s: keys.length === 1 ? keys[0] : keys, i }; - } - #consumeUnquotedIdentifier(query, i) { - if ( query.charCodeAt(i) === 0x2F /* / */ ) { - const r = this.#untilChar(query, 0x2F, i+1); - if ( r === undefined ) { return; } - let re; - try { re = new JSONPath.Regex(r.s); } catch { return; } - return { s: re, i: r.i }; - } - const match = this.#reUnquotedIdentifier.exec(query.slice(i)); - if ( match === null ) { return; } - return { s: match[0], i: i + match[0].length }; - } - #untilChar(query, targetCharCode, i) { - const len = query.length; - const parts = []; - let beg = i, end = i; - for (;;) { - if ( end === len ) { return; } - const c = query.charCodeAt(end); - if ( c === targetCharCode ) { - parts.push(query.slice(beg, end)); - end += 1; - break; - } - if ( c === 0x5C /* \ */ && (end+1) < len ) { - const d = query.charCodeAt(end+1); - if ( d === targetCharCode ) { - parts.push(query.slice(beg, end)); - end += 1; - beg = end; - } - } - end += 1; - } - return { s: parts.join(''), i: end }; - } - #compileExpr(query, step, i) { - if ( query.startsWith('=/', i) ) { - const r = this.#untilChar(query, 0x2F /* / */, i+2); - if ( r === undefined ) { return i; } - const match = /^[i]/.exec(query.slice(r.i)); - try { - step.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); - } catch { return; } - step.op = 're'; - if ( match ) { r.i += match[0].length; } - return r.i; - } - const match = this.#reExpr.exec(query.slice(i)); - if ( match === null ) { return; } - const op = match[1], rval = match[2]; - if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { - const r = this.#untilChar(rval, 0x27, 1); - if ( r === undefined ) { return; } - step.rval = r.s; - step.op = op; - } else { - try { - step.rval = JSON.parse(rval); - step.op = op; - } catch { return; } - } - return i + match[0].length - 1; - } - #resolvePath(path) { - if ( path.length === 0 ) { return { value: this.#root }; } - const key = path.at(-1); - let obj = this.#root - for ( let i = 0, n = path.length-1; i < n; i++ ) { - obj = obj[path[i]]; - if ( obj instanceof Object === false ) { return {}; } - } - return { obj, key, value: obj[key] }; - } - #evaluateExpr(step, owner, k) { - if ( owner === undefined || owner === null ) { return; } - const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); - if ( step.op !== undefined && hasOwn === false ) { return; } - const target = step.not !== true; - const v = owner[k]; - switch ( step.op ) { - case '==': return (v === step.rval) === target; - case '!=': return (v !== step.rval) === target; - case '<': return (v < step.rval) === target; - case '<=': return (v <= step.rval) === target; - case '>': return (v > step.rval) === target; - case '>=': return (v >= step.rval) === target; - case '^=': return `${v}`.startsWith(step.rval) === target; - case '$=': return `${v}`.endsWith(step.rval) === target; - case '*=': return `${v}`.includes(step.rval) === target; - case 're': return step.rval.test(`${v}`); - default: break; - } - return hasOwn === target; - } - #modifyVal(obj, key) { - let { modify, rval } = this.#compiled; - if ( typeof rval === 'string' ) { - rval = rval.replace('${now}', `${Date.now()}`); - } - switch ( modify ) { - case undefined: - obj[key] = rval; - break; - case '+': { - if ( rval instanceof Object === false ) { return; } - const lval = obj[key]; - if ( lval instanceof Object === false ) { return; } - if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of JSONPath.entries(rval) ) { - lval[k] = v; - } - break; - } - case 'call': { - const entries = rval.slice(); - if ( entries.length < 2 ) { break; } - entries.forEach((a, i, aa) => { - if ( a === '${obj}' ) { aa[i] = obj; } - else if ( a === '${key}' ) { aa[i] = key; } - else if ( a === '${val}' ) { aa[i] = obj[key]; } - }); - const instance = entries[0] ?? self; - instance[entries[1]](...entries.slice(2)); - break; - } - case 'repl': { - const lval = obj[key]; - if ( typeof lval !== 'string' ) { return; } - if ( this.#compiled.re === undefined ) { - this.#compiled.re = null; - try { - this.#compiled.re = rval.regex !== undefined - ? new JSONPath.Regex(rval.regex, rval.flags) - : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { } - } - if ( this.#compiled.re === null ) { return; } - obj[key] = lval.replace(this.#compiled.re, rval.replacement); - break; - } - default: - break; - } - } -}; -functions['editOutboundObjectFn'] = function editOutboundObjectFn( - trusted = false, - propChain = '', - jsonq = '' -) { - if ( propChain === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-outbound-object`, - propChain, - jsonq - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - proxyApplyFn(propChain, function(context) { - const obj = context.reflect(); - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { return obj; } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); - } - return objAfter; - }); -}; -functions['editOutboundObject'] = function editOutboundObject(propChain = '', jsonq = '') { - editOutboundObjectFn(false, propChain, jsonq); -}; -functions['trustedEditOutboundObject'] = function trustedEditOutboundObject(propChain = '', jsonq = '') { - editOutboundObjectFn(true, propChain, jsonq); -}; -functions['jsonEditFn'] = function jsonEditFn(trusted = false, jsonq = '', ...varargs) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit`, - jsonq, - ...varargs - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const extraArgs = safe.parseVarargs(varargs); - const pattern = extraArgs.matches && safe.initPattern(extraArgs.matches); - proxyApplyFn('JSON.parse', function(context) { - const json = context.callArgs[0]; - const obj = context.reflect(); - if ( pattern && safe.testPattern(pattern, json) === false ) { return obj; } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { return obj; } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); - } - return objAfter; - }); -}; -functions['jsonEdit'] = function jsonEdit(jsonq = '', ...varargs) { - jsonEditFn(false, jsonq, ...varargs); -}; -functions['trustedJsonEdit'] = function trustedJsonEdit(jsonq = '', ...varargs) { - jsonEditFn(true, jsonq, ...varargs); -}; -functions['editInboundObjectFn'] = function editInboundObjectFn( - trusted = false, - propChain = '', - argPosRaw = '', - jsonq = '', -) { - if ( propChain === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-inbound-object`, - propChain, - jsonq - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const argPos = parseInt(argPosRaw, 10); - if ( isNaN(argPos) ) { return; } - const getArgPos = args => { - if ( Array.isArray(args) === false ) { return; } - if ( argPos >= 0 ) { - if ( args.length <= argPos ) { return; } - return argPos; - } - if ( args.length < -argPos ) { return; } - return args.length + argPos; - }; - const editObj = obj => { - let clone; - try { - clone = safe.JSON_parse(safe.JSON_stringify(obj)); - } catch { - } - if ( typeof clone !== 'object' || clone === null ) { return; } - const objAfter = jsonp.apply(clone); - if ( objAfter === undefined ) { return; } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); - } - return objAfter; - }; - proxyApplyFn(propChain, function(context) { - const i = getArgPos(context.callArgs); - if ( i !== undefined ) { - const obj = editObj(context.callArgs[i]); - if ( obj ) { - context.callArgs[i] = obj; - } - } - return context.reflect(); - }); -}; -functions['editInboundObject'] = function editInboundObject(propChain = '', argPos = '', jsonq = '') { - editInboundObjectFn(false, propChain, argPos, jsonq); -}; -functions['trustedEditInboundObject'] = function trustedEditInboundObject(propChain = '', argPos = '', jsonq = '') { - editInboundObjectFn(true, propChain, argPos, jsonq); -}; -functions['editThisObjectFn'] = function editThisObjectFn( - trusted = false, - propChain = '', - jsonq = '', - order = '' -) { - if ( propChain === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-this-object`, - propChain, - jsonq - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const editObj = objBefore => { - const objAfter = jsonp.apply(objBefore); - if ( objAfter === undefined ) { return; } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel <= 1 ) { return; } - safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); - }; - proxyApplyFn(propChain, function(context) { - const { thisArg } = context; - let r; - if ( order === 'after' ) { - r = context.reflect(); - } - editObj(thisArg); - if ( order !== 'after' ) { - r = context.reflect(); - } - return r; - }); -}; -functions['editThisObject'] = function editThisObject(...args) { - editThisObjectFn(false, ...args); -}; -functions['trustedEditThisObject'] = function trustedEditThisObject(...args) { - editThisObjectFn(true, ...args); -}; -functions['editObjectOnGetterFn'] = function editObjectOnGetterFn( - trusted = false, - propChain = '', - jsonq = '', -) { - if ( propChain === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-object-on-getter`, - propChain, - jsonq - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const editObj = objBefore => { - const objAfter = jsonp.apply(objBefore); - if ( objAfter === undefined ) { return; } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel <= 1 ) { return; } - safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); - }; - let currentValue = trapPropertyFn(propChain, { - get: function() { - if ( currentValue instanceof Object ) { - editObj(currentValue); - } - return currentValue; - }, - set: function(a) { - currentValue = a; - } - }); -}; -functions['editObjectOnGetter'] = function editObjectOnGetter(...args) { - editObjectOnGetterFn(false, ...args); -}; -functions['trustedEditObjectOnGetter'] = function trustedEditObjectOnGetter(...args) { - editObjectOnGetterFn(true, ...args); -}; -functions['editObjectOnSetterFn'] = function editObjectOnSetterFn( - trusted = false, - propChain = '', - jsonq = '', -) { - if ( propChain === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-object-on-setter`, - propChain, - jsonq - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const editObj = objBefore => { - const objAfter = jsonp.apply(objBefore); - if ( objAfter === undefined ) { return; } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel <= 1 ) { return; } - safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); - }; - let currentValue = trapPropertyFn(propChain, { - get: function() { - return currentValue; - }, - set: function(a) { - currentValue = a; - if ( currentValue instanceof Object ) { - editObj(currentValue); - } - } - }); - if ( currentValue instanceof Object ) { - editObj(currentValue); - } -}; -functions['editObjectOnSetter'] = function editObjectOnSetter(...args) { - editObjectOnSetterFn(false, ...args); -}; -functions['trustedEditObjectOnSetter'] = function trustedEditObjectOnSetter(...args) { - editObjectOnSetterFn(true, ...args); -}; -functions['sleepFn'] = function sleepFn(ms = 0) { - const nap = ( ) => { - return new Promise(resolve => { - self.requestAnimationFrame(resolve); - }); - } - const until = Date.now() + ms; - const sleep = async resolve => { - do { - await nap(); - } while ( Date.now() < until ); - resolve(); - }; - return new Promise(resolve => { sleep(resolve); }); -}; -functions['lookupElementsFn'] = function lookupElementsFn(directive, until = 0) { - if ( lookupElementsFn.querySelectorEx === undefined ) { - lookupElementsFn.getShadowRoot = elem => { - if ( elem.openOrClosedShadowRoot ) { // Firefox - return elem.openOrClosedShadowRoot; - } - if ( self.chrome?.dom?.openOrClosedShadowRoot ) { // Chromium - return self.chrome.dom.openOrClosedShadowRoot(elem); - } - return elem.shadowRoot; - }; - lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { - if ( selector.startsWith('xpath:') === false ) { - return Array.from(context.querySelectorAll(selector)); - } - const result = document.evaluate(selector.slice(6), context, null, 7, null); - const out = []; - if ( result.resultType === 7 ) { - for ( let i = 0; i < result.snapshotLength; i++ ) { - out[i] = result.snapshotItem(i); - } - } - return out; - } - lookupElementsFn.querySelectorEx = (selector, context = document) => { - const pos = selector.indexOf(' >>> '); - if ( pos === -1 ) { - return lookupElementsFn.queryOrEvaluateSelector(selector, context); - } - const outside = selector.slice(0, pos).trim(); - const inside = selector.slice(pos + 5).trim(); - const elems = lookupElementsFn.queryOrEvaluateSelector(outside, context); - const out = []; - for ( let i = 0; i < elems.length; i++ ) { - const shadowRoot = lookupElementsFn.getShadowRoot(elems[i]); - if ( Boolean(shadowRoot) === false ) { continue; } - lookupElementsFn.querySelectorEx(inside, shadowRoot).forEach(a => out.push(a)); - } - return out; - }; - lookupElementsFn.lookup = directive => { - const beVisible = directive.startsWith('when-visible:'); - const selector = beVisible ? directive.slice(13) : directive; - const elems = lookupElementsFn.querySelectorEx(selector); - if ( beVisible !== true ) { return elems; } - return elems.filter(a => a.checkVisibility({ - opacityProperty: true, - visibilityProperty: true, - })); - }; - lookupElementsFn.lookupAsync = details => { - const elems = lookupElementsFn.lookup(details.directive); - if ( elems.length || Date.now() >= details.until ) { - if ( details.observer ) { - details.observer.disconnect(); - details.observer = undefined; - } - if ( details.timer ) { - offIdleFn(details.timer); - details.timer = undefined; - } - return details.resolve(elems); - } - if ( details.observer === undefined ) { - details.observer = new MutationObserver(( ) => { - lookupElementsFn.lookupAsync(details); - }); - details.observer.observe(document, { - attributes: true, - childList: true, - subtree: true, - }); - } - if ( details.timer === undefined ) { - details.timer = onIdleFn(( ) => { - details.timer = undefined; - lookupElementsFn.lookupAsync(details); - }, { timeout: 151 }); - } - }; - } - if ( until === 0 ) { - return lookupElementsFn.lookup(directive); - } - return new Promise(resolve => { - lookupElementsFn.lookupAsync({ directive, until, resolve }); - }); -}; -functions['editElementObjectFn'] = async function editElementObjectFn( - trusted = false, - selector = '', - jsonq = '', - timeout = '' -) { - if ( selector === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-element-object`, - selector, jsonq, timeout - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const process = elems => { - for ( const elem of elems ) { - const r = jsonp.apply(elem); - if ( r === undefined ) { continue; } - safe.uboLog(logPrefix, 'Edited'); - } - }; - const until = timeout !== '' ? Date.now() + parseInt(timeout, 10) : 0; - do { - const r = lookupElementsFn(selector, until); - if ( Array.isArray(r) ) { - process(r); - } else if ( r instanceof Promise ) { - const elems = await r; - process(elems); - } - await sleepFn(); - } while ( Date.now() < until ); -}; -functions['editElementObject'] = function editElementObject(...args) { - editElementObjectFn(false, ...args); -}; -functions['trustedEditElementObject'] = function trustedEditElementObject(...args) { - editElementObjectFn(true, ...args); -}; -functions['parsePropertiesToMatchFn'] = function parsePropertiesToMatchFn(propsToMatch, implicit = '') { - const safe = safeSelf(); - const needles = new Map(); - if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } - const options = { canNegate: true }; - for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { - let [ prop, pattern ] = safe.String_split.call(needle, ':'); - if ( prop === '' ) { continue; } - if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { - prop = `${prop}:${pattern}`; - pattern = undefined; - } - if ( pattern !== undefined ) { - needles.set(prop, safe.initPattern(pattern, options)); - } else if ( implicit !== '' ) { - needles.set(implicit, safe.initPattern(prop, options)); - } - } - return needles; -}; -functions['matchObjectPropertiesFn'] = function matchObjectPropertiesFn(propNeedles, ...objs) { - const safe = safeSelf(); - const matched = []; - for ( const obj of objs ) { - if ( obj instanceof Object === false ) { continue; } - for ( const [ prop, details ] of propNeedles ) { - let value = obj[prop]; - if ( value === undefined ) { continue; } - if ( typeof value !== 'string' ) { - try { value = safe.JSON_stringify(value); } - catch { } - if ( typeof value !== 'string' ) { continue; } - } - if ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); - } - } - return matched; -}; -functions['jsonEditXhrResponseFn'] = function jsonEditXhrResponseFn(trusted, jsonq = '', ...varargs) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-xhr-response`, - jsonq - ); - const xhrInstances = new WeakMap(); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const extraArgs = safe.parseVarargs(varargs); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - self.XMLHttpRequest = class extends self.XMLHttpRequest { - open(method, url, ...args) { - const xhrDetails = { method, url }; - const matched = propNeedles.size === 0 || - matchObjectPropertiesFn(propNeedles, xhrDetails); - if ( matched ) { - if ( safe.logLevel > 1 && Array.isArray(matched) ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - xhrInstances.set(this, xhrDetails); - } - return super.open(method, url, ...args); - } - get response() { - const innerResponse = super.response; - const xhrDetails = xhrInstances.get(this); - if ( xhrDetails === undefined ) { return innerResponse; } - const responseLength = typeof innerResponse === 'string' - ? innerResponse.length - : undefined; - if ( xhrDetails.lastResponseLength !== responseLength ) { - xhrDetails.response = undefined; - xhrDetails.lastResponseLength = responseLength; - } - if ( xhrDetails.response !== undefined ) { - return xhrDetails.response; - } - let obj; - if ( typeof innerResponse === 'object' ) { - obj = innerResponse; - } else if ( typeof innerResponse === 'string' ) { - try { obj = safe.JSON_parse(innerResponse); } catch { } - } - if ( typeof obj !== 'object' || obj === null ) { - return (xhrDetails.response = innerResponse); - } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { - return (xhrDetails.response = innerResponse); - } - safe.uboLog(logPrefix, 'Edited'); - const outerResponse = typeof innerResponse === 'string' - ? JSONPath.toJSON(objAfter, safe.JSON_stringify) - : objAfter; - return (xhrDetails.response = outerResponse); - } - get responseText() { - const response = this.response; - return typeof response !== 'string' - ? super.responseText - : response; - } - }; -}; -functions['jsonEditXhrResponse'] = function jsonEditXhrResponse(jsonq = '', ...args) { - jsonEditXhrResponseFn(false, jsonq, ...args); -}; -functions['trustedJsonEditXhrResponse'] = function trustedJsonEditXhrResponse(jsonq = '', ...args) { - jsonEditXhrResponseFn(true, jsonq, ...args); -}; -functions['jsonEditXhrRequestFn'] = function jsonEditXhrRequestFn(trusted, jsonq = '', ...varargs) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-xhr-request`, - jsonq - ); - const xhrInstances = new WeakMap(); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const extraArgs = safe.parseVarargs(varargs); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - self.XMLHttpRequest = class extends self.XMLHttpRequest { - open(method, url, ...args) { - const xhrDetails = { method, url }; - const matched = propNeedles.size === 0 || - matchObjectPropertiesFn(propNeedles, xhrDetails); - if ( matched ) { - if ( safe.logLevel > 1 && Array.isArray(matched) ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - xhrInstances.set(this, xhrDetails); - } - return super.open(method, url, ...args); - } - send(body) { - const xhrDetails = xhrInstances.get(this); - if ( xhrDetails ) { - body = this.#filterBody(body) || body; - } - super.send(body); - } - #filterBody(body) { - if ( typeof body !== 'string' ) { return; } - let data; - try { data = safe.JSON_parse(body); } - catch { } - if ( data instanceof Object === false ) { return; } - const objAfter = jsonp.apply(data); - if ( objAfter === undefined ) { return; } - body = safe.JSON_stringify(objAfter); - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${body}`); - } - return body; - } - }; -}; -functions['jsonEditXhrRequest'] = function jsonEditXhrRequest(jsonq = '', ...args) { - jsonEditXhrRequestFn(false, jsonq, ...args); -}; -functions['trustedJsonEditXhrRequest'] = function trustedJsonEditXhrRequest(jsonq = '', ...args) { - jsonEditXhrRequestFn(true, jsonq, ...args); -}; -functions['collateFetchArgumentsFn'] = function collateFetchArgumentsFn(resource, options) { - const safe = safeSelf(); - const props = [ - 'body', 'cache', 'credentials', 'duplex', 'headers', - 'integrity', 'keepalive', 'method', 'mode', 'priority', - 'redirect', 'referrer', 'referrerPolicy', 'url' - ]; - const out = {}; - if ( collateFetchArgumentsFn.collateKnownProps === undefined ) { - collateFetchArgumentsFn.collateKnownProps = (src, out) => { - for ( const prop of props ) { - if ( src[prop] === undefined ) { continue; } - out[prop] = src[prop]; - } - }; - } - if ( - typeof resource !== 'object' || - safe.Object_toString.call(resource) !== '[object Request]' - ) { - out.url = `${resource}`; - } else { - let clone; - try { - clone = safe.Request_clone.call(resource); - } catch { - } - collateFetchArgumentsFn.collateKnownProps(clone || resource, out); - } - if ( typeof options === 'object' && options !== null ) { - collateFetchArgumentsFn.collateKnownProps(options, out); - } - return out; -}; -functions['jsonEditFetchResponseFn'] = function jsonEditFetchResponseFn(trusted, jsonq = '', ...varargs) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-fetch-response`, - jsonq - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const extraArgs = safe.parseVarargs(varargs); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - proxyApplyFn('fetch', function(context) { - const args = context.callArgs; - const fetchPromise = context.reflect(); - if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(...args); - const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return fetchPromise; } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - } - return fetchPromise.then(responseBefore => { - const response = responseBefore.clone(); - return response.json().then(obj => { - if ( typeof obj !== 'object' ) { return responseBefore; } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { return responseBefore; } - safe.uboLog(logPrefix, 'Edited'); - 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 => { - safe.uboErr(logPrefix, 'Error:', reason); - return responseBefore; - }); - }).catch(reason => { - safe.uboErr(logPrefix, 'Error:', reason); - return fetchPromise; - }); - }); -}; -functions['jsonEditFetchResponse'] = function jsonEditFetchResponse(jsonq = '', ...args) { - jsonEditFetchResponseFn(false, jsonq, ...args); -}; -functions['trustedJsonEditFetchResponse'] = function trustedJsonEditFetchResponse(jsonq = '', ...args) { - jsonEditFetchResponseFn(true, jsonq, ...args); -}; -functions['jsonEditFetchRequestFn'] = function jsonEditFetchRequestFn(trusted, jsonq = '', ...varargs) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-fetch-request`, - jsonq - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const extraArgs = safe.parseVarargs(varargs); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - const filterBody = body => { - if ( typeof body !== 'string' ) { return; } - let data; - try { data = safe.JSON_parse(body); } - catch { } - if ( data instanceof Object === false ) { return; } - const objAfter = jsonp.apply(data); - if ( objAfter === undefined ) { return; } - return safe.JSON_stringify(objAfter); - } - const proxyHandler = context => { - const args = context.callArgs; - const [ resource, options ] = args; - const bodyBefore = options?.body; - if ( Boolean(bodyBefore) === false ) { return context.reflect(); } - const bodyAfter = filterBody(bodyBefore); - if ( bodyAfter === undefined || bodyAfter === bodyBefore ) { - return context.reflect(); - } - if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(resource, options); - const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return context.reflect(); } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${bodyAfter}`); - } - options.body = bodyAfter; - return context.reflect(); - }; - proxyApplyFn('fetch', proxyHandler); - proxyApplyFn('Request', proxyHandler); -}; -functions['jsonEditFetchRequest'] = function jsonEditFetchRequest(jsonq = '', ...args) { - jsonEditFetchRequestFn(false, jsonq, ...args); -}; -functions['trustedJsonEditFetchRequest'] = function trustedJsonEditFetchRequest(jsonq = '', ...args) { - jsonEditFetchRequestFn(true, jsonq, ...args); -}; -functions['jsonlEditFn'] = function jsonlEditFn(jsonp, text = '', jsonExtract) { - const safe = safeSelf(); - const lineSeparator = /\r?\n/.exec(text)?.[0] || '\n'; - const linesBefore = text.split('\n'); - const linesAfter = []; - for ( const lineBefore of linesBefore ) { - const match = jsonExtract.exec(lineBefore); - if ( match === null ) { - linesAfter.push(lineBefore); - continue; - } - const jsonBefore = match[1]; - let obj; - try { obj = safe.JSON_parse(jsonBefore); } catch { } - if ( typeof obj !== 'object' || obj === null ) { - linesAfter.push(lineBefore); - continue; - } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { - linesAfter.push(lineBefore); - continue; - } - const jsonAfter = safe.JSON_stringify(objAfter); - const lineAfter = [ - lineBefore.slice(0, match.index), - jsonAfter, - lineBefore.slice(match.index + jsonBefore.length), - ].join(''); - linesAfter.push(lineAfter); - } - return linesAfter.join(lineSeparator); -}; -functions['jsonlEditXhrResponseFn'] = function jsonlEditXhrResponseFn(trusted, jsonq = '', ...varargs) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}jsonl-edit-xhr-response`, - jsonq - ); - const xhrInstances = new WeakMap(); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const extraArgs = safe.parseVarargs(varargs); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - const jsonExtract = extraArgs.jsonExtract - ? new RegExp(extraArgs.jsonExtract) - : /^(.*)$/; - self.XMLHttpRequest = class extends self.XMLHttpRequest { - open(method, url, ...args) { - const xhrDetails = { method, url }; - const matched = propNeedles.size === 0 || - matchObjectPropertiesFn(propNeedles, xhrDetails); - if ( matched ) { - if ( safe.logLevel > 1 && Array.isArray(matched) ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - xhrInstances.set(this, xhrDetails); - } - return super.open(method, url, ...args); - } - get response() { - const innerResponse = super.response; - const xhrDetails = xhrInstances.get(this); - if ( xhrDetails === undefined ) { - return innerResponse; - } - const responseLength = typeof innerResponse === 'string' - ? innerResponse.length - : undefined; - if ( xhrDetails.lastResponseLength !== responseLength ) { - xhrDetails.response = undefined; - xhrDetails.lastResponseLength = responseLength; - } - if ( xhrDetails.response !== undefined ) { - return xhrDetails.response; - } - if ( typeof innerResponse !== 'string' ) { - return (xhrDetails.response = innerResponse); - } - const outerResponse = jsonlEditFn(jsonp, innerResponse, jsonExtract); - if ( outerResponse !== innerResponse ) { - safe.uboLog(logPrefix, 'Pruned'); - } - return (xhrDetails.response = outerResponse); - } - get responseText() { - const response = this.response; - return typeof response !== 'string' - ? super.responseText - : response; - } - }; -}; -functions['jsonlEditXhrResponse'] = function jsonlEditXhrResponse(jsonq = '', ...args) { - jsonlEditXhrResponseFn(false, jsonq, ...args); -}; -functions['trustedJsonlEditXhrResponse'] = function trustedJsonlEditXhrResponse(jsonq = '', ...args) { - jsonlEditXhrResponseFn(true, jsonq, ...args); -}; -functions['jsonlEditFetchResponseFn'] = function jsonlEditFetchResponseFn(trusted, jsonq = '', ...varargs) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}jsonl-edit-fetch-response`, - jsonq - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const extraArgs = safe.parseVarargs(varargs); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - const jsonExtract = extraArgs.jsonExtract - ? new RegExp(extraArgs.jsonExtract) - : /^(.*)$/; - const logall = jsonq === ''; - proxyApplyFn('fetch', function(context) { - const args = context.callArgs; - const fetchPromise = context.reflect(); - if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(...args); - const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return fetchPromise; } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - } - return fetchPromise.then(responseBefore => { - const response = responseBefore.clone(); - return response.text().then(textBefore => { - if ( typeof textBefore !== 'string' ) { return textBefore; } - if ( logall ) { - safe.uboLog(logPrefix, textBefore); - return responseBefore; - } - const textAfter = jsonlEditFn(jsonp, textBefore, jsonExtract); - if ( textAfter === textBefore ) { return responseBefore; } - safe.uboLog(logPrefix, 'Pruned'); - 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 => { - safe.uboErr(logPrefix, 'Error:', reason); - return responseBefore; - }); - }).catch(reason => { - safe.uboErr(logPrefix, 'Error:', reason); - return fetchPromise; - }); - }); -}; -functions['jsonlEditFetchResponse'] = function jsonlEditFetchResponse(jsonq = '', ...args) { - jsonlEditFetchResponseFn(false, jsonq, ...args); -}; -functions['trustedJsonlEditFetchResponse'] = function trustedJsonlEditFetchResponse(jsonq = '', ...args) { - jsonlEditFetchResponseFn(true, jsonq, ...args); -}; -functions['matchesStackTraceFn'] = function matchesStackTraceFn( - needleDetails, - logLevel = '' -) { - const safe = safeSelf(); - const exceptionToken = getExceptionTokenFn(); - 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 safe.String_split.call(error.stack, /[\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('') ) { - 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'); - const r = needleDetails.matchAll !== true && - safe.testPattern(needleDetails, stack); - if ( - logLevel === 'all' || - logLevel === 'match' && r || - logLevel === 'nomatch' && !r - ) { - safe.uboLog(stack.replace(/\t/g, '\n')); - } - return r; -}; -functions['abortOnStackTrace'] = function abortOnStackTrace( - chain = '', - needle = '', - ...varargs -) { - if ( typeof chain !== 'string' ) { return; } - const safe = safeSelf(); - const needleDetails = safe.initPattern(needle, { canNegate: true }); - const extraArgs = safe.parseVarargs(varargs); - if ( needle === '' ) { extraArgs.log = 'all'; } - const makeProxy = function(owner, chain) { - const pos = chain.indexOf('.'); - if ( pos === -1 ) { - let v = owner[chain]; - Object.defineProperty(owner, chain, { - get: function() { - const log = safe.logLevel > 1 ? 'all' : 'match'; - if ( matchesStackTraceFn(needleDetails, log) ) { - throw new ReferenceError(getExceptionTokenFn()); - } - return v; - }, - set: function(a) { - const log = safe.logLevel > 1 ? 'all' : 'match'; - if ( matchesStackTraceFn(needleDetails, log) ) { - throw new ReferenceError(getExceptionTokenFn()); - } - 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); -}; -functions['objectPruneFn'] = function objectPruneFn( - obj, - rawPrunePaths, - rawNeedlePaths, - stackNeedleDetails = { matchAll: true }, - extraArgs = {} -) { - if ( typeof rawPrunePaths !== 'string' ) { return; } - const safe = safeSelf(); - const prunePaths = rawPrunePaths !== '' - ? safe.String_split.call(rawPrunePaths, / +/) - : []; - const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== '' - ? safe.String_split.call(rawNeedlePaths, / +/) - : []; - if ( stackNeedleDetails.matchAll !== true ) { - if ( matchesStackTraceFn(stackNeedleDetails, extraArgs.logstack) === false ) { - return; - } - } - if ( objectPruneFn.mustProcess === undefined ) { - objectPruneFn.mustProcess = (root, needlePaths) => { - for ( const needlePath of needlePaths ) { - if ( objectFindOwnerFn(root, needlePath) === false ) { - return false; - } - } - return true; - }; - } - if ( prunePaths.length === 0 ) { return; } - let outcome = 'nomatch'; - if ( objectPruneFn.mustProcess(obj, needlePaths) ) { - for ( const path of prunePaths ) { - if ( objectFindOwnerFn(obj, path, true) ) { - outcome = 'match'; - } - } - } - if ( outcome === 'match' ) { return obj; } -}; -functions['objectFindOwnerFn'] = function objectFindOwnerFn( - root, - path, - prune = false -) { - const safe = safeSelf(); - 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 safe.Object_hasOwn(owner, chain); - } - let modified = false; - if ( chain === '*' ) { - for ( const key in owner ) { - if ( safe.Object_hasOwn(owner, key) === false ) { continue; } - delete owner[key]; - modified = true; - } - } else if ( safe.Object_hasOwn(owner, chain) ) { - delete owner[chain]; - modified = true; - } - return modified; - } - const prop = chain.slice(0, pos); - 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; - } - if ( - prop === '[]' && Array.isArray(owner) || - prop === '{}' && owner instanceof Object || - prop === '*' && owner instanceof Object - ) { - for ( const key of Object.keys(owner) ) { - if (objectFindOwnerFn(owner[key], next, prune) === false ) { continue; } - found = true; - } - return found; - } - if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } - owner = owner[prop]; - chain = chain.slice(pos + 1); - } -}; -functions['trustedPruneInboundObject'] = function trustedPruneInboundObject( - entryPoint = '', - argPos = '', - rawPrunePaths = '', - rawNeedlePaths = '', - ...varargs -) { - 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.parseVarargs(varargs); - const needlePaths = []; - if ( rawPrunePaths !== '' ) { - needlePaths.push(...safe.String_split.call(rawPrunePaths, / +/)); - } - if ( rawNeedlePaths !== '' ) { - needlePaths.push(...safe.String_split.call(rawNeedlePaths, / +/)); - } - const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); - const mustProcess = root => { - for ( const needlePath of needlePaths ) { - if ( objectFindOwnerFn(root, needlePath) === false ) { - return false; - } - } - return true; - }; - context[prop] = new Proxy(context[prop], { - apply: function(target, thisArg, args) { - const targetArg = argIndex <= args.length - ? args[argIndex-1] - : undefined; - 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, - stackNeedle, - extraArgs - ); - args[argIndex-1] = objAfter || objBefore; - } - } - return Reflect.apply(target, thisArg, args); - }, - }); -}; -functions['trustedPruneOutboundObject'] = function trustedPruneOutboundObject( - propChain = '', - rawPrunePaths = '', - rawNeedlePaths = '', - ...varargs -) { - if ( propChain === '' ) { return; } - const safe = safeSelf(); - const extraArgs = safe.parseVarargs(varargs); - proxyApplyFn(propChain, function(context) { - const objBefore = context.reflect(); - if ( objBefore instanceof Object === false ) { return objBefore; } - const objAfter = objectPruneFn( - objBefore, - rawPrunePaths, - rawNeedlePaths, - { matchAll: true }, - extraArgs - ); - return objAfter || objBefore; - }); -}; -functions['jsonPrune'] = function jsonPrune( - rawPrunePaths = '', - rawNeedlePaths = '', - stackNeedle = '', - ...varargs -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('json-prune', rawPrunePaths, rawNeedlePaths, stackNeedle); - const stackNeedleDetails = safe.initPattern(stackNeedle, { canNegate: true }); - const extraArgs = safe.parseVarargs(varargs); - proxyApplyFn('JSON.parse', function(context) { - const objBefore = context.reflect(); - if ( rawPrunePaths === '' ) { - safe.uboLog(logPrefix, safe.JSON_stringify(objBefore, null, 2)); - } - const objAfter = objectPruneFn( - objBefore, - rawPrunePaths, - rawNeedlePaths, - stackNeedleDetails, - extraArgs - ); - if ( objAfter === undefined ) { return objBefore; } - safe.uboLog(logPrefix, 'Pruned'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After pruning:\n${safe.JSON_stringify(objAfter, null, 2)}`); - } - return objAfter; - }); -}; -functions['jsonPruneFetchResponse'] = function jsonPruneFetchResponse( - rawPrunePaths = '', - rawNeedlePaths = '', - ...varargs -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('json-prune-fetch-response', rawPrunePaths, rawNeedlePaths); - const extraArgs = safe.parseVarargs(varargs); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); - const logall = rawPrunePaths === ''; - const applyHandler = function(target, thisArg, args) { - const fetchPromise = Reflect.apply(target, thisArg, args); - if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(...args); - const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return fetchPromise; } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - } - return fetchPromise.then(responseBefore => { - const response = responseBefore.clone(); - return response.json().then(objBefore => { - if ( typeof objBefore !== 'object' ) { return responseBefore; } - if ( logall ) { - safe.uboLog(logPrefix, safe.JSON_stringify(objBefore, null, 2)); - return responseBefore; - } - const objAfter = objectPruneFn( - objBefore, - rawPrunePaths, - rawNeedlePaths, - stackNeedle, - extraArgs - ); - if ( typeof objAfter !== 'object' ) { return responseBefore; } - safe.uboLog(logPrefix, 'Pruned'); - 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 => { - safe.uboErr(logPrefix, 'Error:', reason); - return responseBefore; - }); - }).catch(reason => { - safe.uboErr(logPrefix, 'Error:', reason); - return fetchPromise; - }); - }; - self.fetch = new Proxy(self.fetch, { - apply: applyHandler - }); -}; -functions['jsonPruneXhrResponse'] = function jsonPruneXhrResponse( - rawPrunePaths = '', - rawNeedlePaths = '', - ...varargs -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('json-prune-xhr-response', rawPrunePaths, rawNeedlePaths); - const xhrInstances = new WeakMap(); - const extraArgs = safe.parseVarargs(varargs); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); - self.XMLHttpRequest = class extends self.XMLHttpRequest { - open(method, url, ...args) { - const xhrDetails = { method, url }; - let outcome = 'match'; - if ( propNeedles.size !== 0 ) { - if ( matchObjectPropertiesFn(propNeedles, xhrDetails) === undefined ) { - outcome = 'nomatch'; - } - } - if ( outcome === 'match' ) { - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched optional "propsToMatch", "${extraArgs.propsToMatch}"`); - } - xhrInstances.set(this, xhrDetails); - } - return super.open(method, url, ...args); - } - get response() { - const innerResponse = super.response; - const xhrDetails = xhrInstances.get(this); - if ( xhrDetails === undefined ) { - return innerResponse; - } - const responseLength = typeof innerResponse === 'string' - ? innerResponse.length - : undefined; - if ( xhrDetails.lastResponseLength !== responseLength ) { - xhrDetails.response = undefined; - xhrDetails.lastResponseLength = responseLength; - } - if ( xhrDetails.response !== undefined ) { - return xhrDetails.response; - } - let objBefore; - if ( typeof innerResponse === 'object' ) { - objBefore = innerResponse; - } else if ( typeof innerResponse === 'string' ) { - try { - objBefore = safe.JSON_parse(innerResponse); - } catch { - } - } - if ( typeof objBefore !== 'object' ) { - return (xhrDetails.response = innerResponse); - } - const objAfter = objectPruneFn( - objBefore, - rawPrunePaths, - rawNeedlePaths, - stackNeedle, - extraArgs - ); - let outerResponse; - if ( typeof objAfter === 'object' ) { - outerResponse = typeof innerResponse === 'string' - ? safe.JSON_stringify(objAfter) - : objAfter; - safe.uboLog(logPrefix, 'Pruned'); - } else { - outerResponse = innerResponse; - } - return (xhrDetails.response = outerResponse); - } - get responseText() { - const response = this.response; - return typeof response !== 'string' - ? super.responseText - : response; - } - }; -}; -functions['evaldataPrune'] = function evaldataPrune( - rawPrunePaths = '', - rawNeedlePaths = '' -) { - proxyApplyFn('eval', function(context) { - const before = context.reflect(); - if ( typeof before !== 'object' ) { return before; } - if ( before === null ) { return null; } - const after = objectPruneFn(before, rawPrunePaths, rawNeedlePaths); - return after || before; - }); -}; -functions['noEvalIf'] = function noEvalIf( - needle = '' -) { - if ( typeof needle !== 'string' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('noeval-if', needle); - const reNeedle = safe.patternToRegex(needle); - proxyApplyFn('eval', function(context) { - const { callArgs } = context; - const a = String(callArgs[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 context.reflect(); - }); -}; -functions['preventAddEventListener'] = function preventAddEventListener( - type = '', - pattern = '', - ...varargs -) { - const safe = safeSelf(); - const extraArgs = safe.parseVarargs(varargs); - const logPrefix = safe.makeLogPrefix('prevent-addEventListener', type, pattern); - const reType = safe.patternToRegex(type, undefined, true); - const rePattern = safe.patternToRegex(pattern); - const targetSelector = extraArgs.elements || undefined; - const elementMatches = elem => { - if ( targetSelector === 'window' ) { return elem === window; } - if ( targetSelector === 'document' ) { return elem === document; } - 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 = []; - // https://github.com/uBlockOrigin/uAssets/discussions/17907#discussioncomment-9871079 - const id = String(elem.id); - if ( id !== '' ) { parts.push(`#${CSS.escape(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}"]`); - } - return parts.join(''); - }; - const shouldPrevent = (thisArg, type, handler) => { - const matchesType = safe.RegExp_test(reType, type); - const matchesHandler = safe.RegExp_test(rePattern, handler); - const matchesEither = matchesType || matchesHandler; - const matchesBoth = matchesType && matchesHandler; - if ( safe.logLevel > 1 && matchesEither ) { - debugger; // eslint-disable-line no-debugger - } - if ( matchesBoth && targetSelector !== undefined ) { - if ( elementMatches(thisArg) === false ) { return false; } - } - return matchesBoth; - }; - const proxyFn = function(context) { - const { callArgs, thisArg } = context; - let t, h; - try { - t = String(callArgs[0]); - if ( typeof callArgs[1] === 'function' ) { - h = String(safe.Function_toString(callArgs[1])); - } else if ( typeof callArgs[1] === 'object' && callArgs[1] !== null ) { - if ( typeof callArgs[1].handleEvent === 'function' ) { - h = String(safe.Function_toString(callArgs[1].handleEvent)); - } - } else { - h = String(callArgs[1]); - } - } catch { - } - if ( type === '' && pattern === '' ) { - safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`); - } else if ( shouldPrevent(thisArg, t, h) ) { - return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`); - } - return context.reflect(); - }; - const protect = owner => { - const { addEventListener } = owner; - Object.defineProperty(owner, 'addEventListener', { - set() { }, - get() { return addEventListener; } - }); - }; - runAt(( ) => { - proxyApplyFn('EventTarget.prototype.addEventListener', proxyFn); - if ( extraArgs.protect ) { protect(EventTarget.prototype); } - if ( Object.hasOwn(document, 'addEventListener') ) { - proxyApplyFn('document.addEventListener', proxyFn); - if ( extraArgs.protect ) { protect(document); } - } - if ( Object.hasOwn(window, 'addEventListener') ) { - proxyApplyFn('window.addEventListener', proxyFn); - if ( extraArgs.protect ) { protect(window); } - } - }, extraArgs.runAt); -}; -functions['preventBab'] = function preventBab() { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('prevent-bab'); - const signatures = [ - [ 'blockadblock' ], - [ 'babasbm' ], - [ /getItem\('babn'\)/ ], - [ - 'getElementById', - 'String.fromCharCode', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', - 'charAt', - 'DOMContentLoaded', - 'AdBlock', - 'addEventListener', - 'doScroll', - 'fromCharCode', - '<<2|r>>4', - 'sessionStorage', - 'clientWidth', - 'localStorage', - 'Math', - 'random', - ], - ]; - const check = function(s) { - if ( typeof s !== 'string' ) { return false; } - for ( const tokens of signatures ) { - let match = 0; - for ( const token of tokens ) { - const hit = token instanceof RegExp - ? token.test(s) - : s.includes(token); - if ( hit ) { match += 1; } - } - if ( (match / tokens.length) >= 0.8 ) { return true; } - } - return false; - }; - proxyApplyFn('eval', function(context) { - const a = context.callArgs[0]; - if ( !check(a) ) { - return context.reflect(); - } - safe.uboLog(logPrefix, 'Prevented'); - if ( document.body ) { - document.body.style.removeProperty('visibility'); - } - const el = document.getElementById('babasbmsgx'); - if ( el ) { - el.parentNode.removeChild(el); - } - }); - proxyApplyFn('setTimeout', function(context) { - const { callArgs } = context; - const a = callArgs[0]; - if ( typeof a === 'string' && /\.bab_elementid.$/.test(a) ) { - callArgs[0] = ( ) => { }; - safe.uboLog(logPrefix, 'Prevented'); - } - return context.reflect(); - }); -}; -functions['preventClipboardWrite'] = function preventClipboardWrite(matches = '', ...varargs) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('prevent-clipboard-write'); - const pattern = safe.initPattern(matches); - const extraArgs = safe.parseVarargs(varargs); - const excludePattern = extraArgs.excludeMatches && - safe.initPattern(extraArgs.excludeMatches); - const htmlTemplate = [ - '
', - '${warning}\n', - '', - '
', - ].join(''); - const domAlert = clipboardText => { - const doc = document; - const domAlert = extraArgs.domAlert.replace(/\\n/g, '\n'); - let html; - if ( domAlert.includes('${text}') ) { - const code = doc.createElement('code'); - const styles = [ - 'background-color: #ddc', - 'display: inline-block', - 'font-family: monospace', - 'font-size: 100%', - 'max-height: 8em', - 'overflow: auto', - 'padding: 0.25em', - 'user-select: all', - 'width: 100%;', - 'word-break: break-all' - ]; - if ( Boolean(extraArgs.selectable ?? true) === false ) { - styles.push('user-select: none'); - } - code.style = styles.join(';'); - code.textContent = clipboardText; - html = htmlTemplate.replace('${warning}', - domAlert.replace('${text}', code.outerHTML) - ); - } else { - html = htmlTemplate.replace('${warning}', domAlert); - } - if ( currentAlert ) { currentAlert.remove(); } - const domParser = new DOMParser(); - const fragment = domParser.parseFromString(html, 'text/html'); - currentAlert = fragment.querySelector('div'); - const button = currentAlert.querySelector('button'); - button.addEventListener('click', ( ) => { - if ( currentAlert === null ) { return; } - currentAlert.remove(); - currentAlert = null; - }); - currentAlert.setAttribute('popover', 'manual'); - doc.documentElement.append(currentAlert); - if ( typeof currentAlert.showPopover === 'function' ) { - currentAlert.showPopover(); - } - }; - let currentAlert = null; - const prevent = text => { - if ( typeof text !== 'string' ) { return; } - text = text.trim(); - if ( safe.testPattern(pattern, text) !== true ) { return; } - if ( extraArgs.excludeMatches ) { - if ( safe.testPattern(excludePattern, text) ) { return; } - } - if ( extraArgs.domAlert ) { - domAlert(text); - } - safe.uboLog(logPrefix, 'Prevented:\n\t', text); - return true; - }; - const installTraps = ( ) => { - proxyApplyFn('navigator.clipboard.writeText', async function(context) { - const text = `${context.callArgs[0]}`; - if ( prevent(text) ) { return; } - return context.reflect(); - }, { skipToString: true }); - proxyApplyFn('document.execCommand', function(context) { - const { callArgs } = context; - if ( callArgs[0] === 'copy' || callArgs[0] === 'cut' ) { - const text = document.getSelection()?.toString(); - if ( prevent(text) ) { return true; } - } - return context.reflect(); - }, { skipToString: true }); - }; - runAt(( ) => { - self.document.addEventListener('mousemove', installTraps, { - once: true, - capture: true, - }); - }, 'interactive') -}; -functions['preventDialog'] = function preventDialog( - selector = '', -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('prevent-dialog', selector); - const prevent = ( ) => { - debouncer = undefined; - const elems = document.querySelectorAll(`dialog${selector}`); - for ( const elem of elems ) { - if ( typeof elem.close !== 'function' ) { continue; } - if ( elem.open === false ) { continue; } - elem.close(); - safe.uboLog(logPrefix, 'Closed'); - } - }; - let debouncer; - const observer = new MutationObserver(( ) => { - if ( debouncer !== undefined ) { return; } - debouncer = requestAnimationFrame(prevent); - }); - observer.observe(document, { - attributes: true, - childList: true, - subtree: true, - }); -}; -functions['generateContentFn'] = function generateContentFn(trusted, 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 randomize(10); - } - if ( directive === 'emptyObj' ) { - return '{}'; - } - if ( directive === 'emptyArr' ) { - return '[]'; - } - if ( directive === 'emptyStr' ) { - return ''; - } - if ( directive.startsWith('length:') ) { - const match = /^length:(\d+)(?:-(\d+))?$/.exec(directive); - if ( match === null ) { return ''; } - 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 randomize(len | 0); - } - if ( directive.startsWith('war:') ) { - if ( scriptletGlobals.warOrigin === undefined ) { return ''; } - return new Promise(resolve => { - const warOrigin = scriptletGlobals.warOrigin; - const warName = directive.slice(4); - const fullpath = [ warOrigin, '/', warName ]; - const warSecret = scriptletGlobals.warSecret; - 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(); - }).catch(( ) => ''); - } - if ( directive.startsWith('join:') ) { - const parts = directive.slice(7) - .split(directive.slice(5, 7)) - .map(a => generateContentFn(trusted, a)); - return parts.some(a => a instanceof Promise) - ? Promise.all(parts).then(parts => parts.join('')) - : parts.join(''); - } - if ( trusted ) { - return directive; - } - return ''; -}; -functions['preventFetchFn'] = function preventFetchFn( - trusted = false, - propsToMatch = '', - responseBody = '', - responseType = '', - ...varargs -) { - const safe = safeSelf(); - const setTimeout = self.setTimeout; - const scriptletName = `${trusted ? 'trusted-' : ''}prevent-fetch`; - const logPrefix = safe.makeLogPrefix( - scriptletName, - propsToMatch, - responseBody, - responseType - ); - const extraArgs = safe.parseVarargs(varargs); - const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url'); - const validResponseProps = { - ok: [ false, true ], - status: [ 403 ], - statusText: [ '', 'Not Found' ], - type: [ 'basic', 'cors', 'default', 'error', 'opaque' ], - }; - const responseProps = { - statusText: { value: 'OK' }, - }; - const responseHeaders = {}; - if ( /^\{.*\}$/.test(responseType) ) { - try { - Object.entries(JSON.parse(responseType)).forEach(([ p, v ]) => { - if ( p === 'headers' && trusted ) { - Object.assign(responseHeaders, v); - return; - } - if ( validResponseProps[p] === undefined ) { return; } - if ( validResponseProps[p].includes(v) === false ) { return; } - responseProps[p] = { value: v }; - }); - } - catch { } - } else if ( responseType !== '' ) { - if ( validResponseProps.type.includes(responseType) ) { - responseProps.type = { value: responseType }; - } - } - proxyApplyFn('fetch', function fetch(context) { - const { callArgs } = context; - const details = collateFetchArgumentsFn(...callArgs); - if ( safe.logLevel > 1 || propsToMatch === '' && responseBody === '' ) { - const out = Array.from(Object.entries(details)).map(a => `${a[0]}:${a[1]}`); - safe.uboLog(logPrefix, `Called: ${out.join('\n')}`); - } - if ( propsToMatch === '' && responseBody === '' ) { - return context.reflect(); - } - const matched = matchObjectPropertiesFn(propNeedles, details); - if ( matched === undefined || matched.length === 0 ) { - return context.reflect(); - } - return Promise.resolve(generateContentFn(trusted, responseBody)).then(text => { - safe.uboLog(logPrefix, `Prevented with response "${text}"`); - const headers = Object.assign({}, responseHeaders); - if ( headers['content-length'] === undefined ) { - headers['content-length'] = text.length; - } - const response = new Response(text, { headers }); - const props = Object.assign( - { url: { value: details.url } }, - responseProps - ); - safe.Object_defineProperties(response, props); - if ( extraArgs.throttle ) { - return new Promise(resolve => { - setTimeout(( ) => { resolve(response); }, extraArgs.throttle); - }); - } - return response; - }); - }); -}; -functions['preventFetch'] = function preventFetch(...args) { - preventFetchFn(false, ...args); -}; -functions['trustedPreventFetch'] = function trustedPreventFetch(...args) { - preventFetchFn(true, ...args); -}; -functions['freezeElementProperty'] = function freezeElementProperty( - property = '', - selector = '', - pattern = '' -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('freeze-element-property', property, selector, pattern); - const matcher = safe.initPattern(pattern, { canNegate: true }); - const owner = (( ) => { - if ( Object.hasOwn(HTMLScriptElement.prototype, property) ) { - return HTMLScriptElement.prototype; - } - if ( Object.hasOwn(HTMLElement.prototype, property) ) { - return HTMLElement.prototype; - } - if ( Object.hasOwn(Element.prototype, property) ) { - return Element.prototype; - } - if ( Object.hasOwn(Node.prototype, property) ) { - return Node.prototype; - } - return null; - })(); - if ( owner === null ) { return; } - const current = safe.Object_getOwnPropertyDescriptor(owner, property); - if ( current === undefined ) { return; } - const shouldPreventSet = (elem, a) => { - if ( selector !== '' ) { - if ( typeof elem.matches !== 'function' ) { return false; } - if ( elem.matches(selector) === false ) { return false; } - } - return safe.testPattern(matcher, `${a}`); - }; - Object.defineProperty(owner, property, { - get: function() { - return current.get - ? current.get.call(this) - : current.value; - }, - set: function(a) { - if ( shouldPreventSet(this, a) ) { - safe.uboLog(logPrefix, 'Assignment prevented'); - } else if ( current.set ) { - current.set.call(this, a); - } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Assigned:\n${a}`); - } - current.value = a; - }, - }); -}; -functions['preventInnerHTML'] = function preventInnerHTML( - selector = '', - pattern = '' -) { - freezeElementProperty('innerHTML', selector, pattern); -}; -functions['preventNavigation'] = function preventNavigation( - pattern = '' -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('prevent-navigation', pattern); - const needle = pattern === 'location.href' ? self.location.href : pattern; - const matcher = safe.initPattern(needle, { canNegate: true }); - self.navigation.addEventListener('navigate', ev => { - if ( ev.userInitiated ) { return; } - const { url } = ev.destination; - if ( pattern === '' ) { - safe.uboLog(logPrefix, `Navigation to ${url}`); - return; - } - if ( safe.testPattern(matcher, url) ) { - ev.preventDefault(); - safe.uboLog(logPrefix, `Prevented navigation to ${url}`); - } - }); -}; -functions['RangeParser'] = class RangeParser { - constructor(s) { - this.not = s.charAt(0) === '!'; - if ( this.not ) { s = s.slice(1); } - if ( s === '' ) { return; } - const pos = s.indexOf('-'); - if ( pos !== 0 ) { - this.min = this.max = parseInt(s, 10) || 0; - } - if ( pos !== -1 ) { - this.max = parseInt(s.slice(pos + 1), 10) || Number.MAX_SAFE_INTEGER; - } - } - unbound() { - return this.min === undefined && this.max === undefined; - } - test(v) { - const n = Math.min(Math.max(Number(v) || 0, 0), Number.MAX_SAFE_INTEGER); - if ( this.min === this.max ) { - return (this.min === undefined || n === this.min) !== this.not; - } - if ( this.min === undefined ) { - return (n <= this.max) !== this.not; - } - if ( this.max === undefined ) { - return (n >= this.min) !== this.not; - } - return (n >= this.min && n <= this.max) !== this.not; - } -}; -functions['preventSetTimeout'] = function preventSetTimeout( - needleRaw = '', - delayRaw = '' -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('prevent-setTimeout', needleRaw, delayRaw); - const needleNot = needleRaw.charAt(0) === '!'; - const reNeedle = safe.patternToRegex(needleNot ? needleRaw.slice(1) : needleRaw); - const range = new RangeParser(delayRaw); - proxyApplyFn('setTimeout', function(context) { - const { callArgs } = context; - const a = callArgs[0] instanceof Function - ? safe.String(safe.Function_toString(callArgs[0])) - : safe.String(callArgs[0]); - const b = callArgs[1]; - if ( needleRaw === '' && range.unbound() ) { - safe.uboLog(logPrefix, `Called:\n${a}\n${b}`); - return context.reflect(); - } - if ( reNeedle.test(a) !== needleNot && range.test(b) ) { - callArgs[0] = function(){}; - safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`); - } - return context.reflect(); - }); -}; -functions['preventSetInterval'] = function preventSetInterval( - needleRaw = '', - delayRaw = '' -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('prevent-setInterval', needleRaw, delayRaw); - const needleNot = needleRaw.charAt(0) === '!'; - const reNeedle = safe.patternToRegex(needleNot ? needleRaw.slice(1) : needleRaw); - const range = new RangeParser(delayRaw); - proxyApplyFn('setInterval', function(context) { - const { callArgs } = context; - const a = callArgs[0] instanceof Function - ? safe.String(safe.Function_toString(callArgs[0])) - : safe.String(callArgs[0]); - const b = callArgs[1]; - if ( needleRaw === '' && range.unbound() ) { - safe.uboLog(logPrefix, `Called:\n${a}\n${b}`); - return context.reflect(); - } - if ( reNeedle.test(a) !== needleNot && range.test(b) ) { - callArgs[0] = function(){}; - safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`); - } - return context.reflect(); - }); -}; -functions['preventRequestAnimationFrame'] = function preventRequestAnimationFrame( - needleRaw = '' -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('prevent-requestAnimationFrame', needleRaw); - const needleNot = needleRaw.charAt(0) === '!'; - const reNeedle = safe.patternToRegex(needleNot ? needleRaw.slice(1) : needleRaw); - proxyApplyFn('requestAnimationFrame', function(context) { - const { callArgs } = context; - const a = callArgs[0] instanceof Function - ? safe.String(safe.Function_toString(callArgs[0])) - : safe.String(callArgs[0]); - if ( needleRaw === '' ) { - safe.uboLog(logPrefix, `Called:\n${a}`); - } else if ( reNeedle.test(a) !== needleNot ) { - callArgs[0] = function(){}; - safe.uboLog(logPrefix, `Prevented:\n${a}`); - } - return context.reflect(); - }); -}; -functions['validateConstantFn'] = function validateConstantFn(trusted, raw, extraArgs = {}) { - const safe = safeSelf(); - 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 ( raw === 'throwFunc' ) { - value = function(){ throw ''; }; - } else if ( /^-?\d+$/.test(raw) ) { - value = parseInt(raw); - if ( isNaN(raw) ) { return; } - if ( Math.abs(raw) > 0x7FFF ) { return; } - } else if ( trusted ) { - if ( raw.startsWith('json:') ) { - try { value = safe.JSON_parse(raw.slice(5)); } catch { return; } - } else if ( raw.startsWith('{') && raw.endsWith('}') ) { - try { value = safe.JSON_parse(raw).value; } catch { 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; -}; -functions['setConstantFn'] = function setConstantFn( - trusted = false, - chain = '', - rawValue = '', - ...varargs -) { - if ( chain === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('set-constant', chain, rawValue); - const extraArgs = safe.parseVarargs(varargs); - function setConstant(chain, rawValue) { - const trappedProp = (( ) => { - const pos = chain.lastIndexOf('.'); - if ( pos === -1 ) { return chain; } - return chain.slice(pos+1); - })(); - const cloakFunc = fn => { - safe.Object_defineProperty(fn, 'name', { value: trappedProp }); - return new Proxy(fn, { - defineProperty(target, prop) { - if ( prop !== 'toString' ) { - return Reflect.defineProperty(...arguments); - } - return true; - }, - deleteProperty(target, prop) { - if ( prop !== 'toString' ) { - return Reflect.deleteProperty(...arguments); - } - return true; - }, - get(target, prop) { - if ( prop === 'toString' ) { - return function() { - return `function ${trappedProp}() { [native code] }`; - }.bind(null); - } - return Reflect.get(...arguments); - }, - }); - }; - if ( trappedProp === '' ) { return; } - const thisScript = document.currentScript; - let normalValue = validateConstantFn(trusted, rawValue, extraArgs); - if ( rawValue === 'noopFunc' || rawValue === 'trueFunc' || rawValue === 'falseFunc' ) { - normalValue = cloakFunc(normalValue); - } - let aborted = false; - const mustAbort = function(v) { - if ( trusted ) { return false; } - if ( aborted ) { return true; } - aborted = - (v !== undefined && v !== null) && - (normalValue !== undefined && normalValue !== null) && - (typeof v !== typeof normalValue); - if ( aborted ) { - safe.uboLog(logPrefix, `Aborted because value set to ${v}`); - } - return aborted; - }; - // https://github.com/uBlockOrigin/uBlock-issues/issues/156 - // Support multiple trappers for the same property. - const trapProp = function(owner, prop, configurable, handler) { - if ( handler.init(configurable ? owner[prop] : normalValue) === false ) { return; } - const odesc = safe.Object_getOwnPropertyDescriptor(owner, prop); - let prevGetter, prevSetter; - if ( odesc instanceof safe.Object ) { - owner[prop] = normalValue; - if ( odesc.get instanceof Function ) { - prevGetter = odesc.get; - } - if ( odesc.set instanceof Function ) { - prevSetter = odesc.set; - } - } - try { - safe.Object_defineProperty(owner, prop, { - configurable, - get() { - if ( prevGetter !== undefined ) { - prevGetter(); - } - return handler.getter(); - }, - set(a) { - if ( prevSetter !== undefined ) { - prevSetter(a); - } - handler.setter(a); - } - }); - safe.uboLog(logPrefix, 'Trap installed'); - } catch(ex) { - safe.uboErr(logPrefix, ex); - } - }; - const trapChain = function(owner, chain) { - const pos = chain.indexOf('.'); - if ( pos === -1 ) { - trapProp(owner, chain, false, { - v: undefined, - init: function(v) { - if ( mustAbort(v) ) { return false; } - this.v = v; - return true; - }, - getter: function() { - if ( document.currentScript === thisScript ) { - return this.v; - } - safe.uboLog(logPrefix, 'Property read'); - return normalValue; - }, - setter: function(a) { - if ( mustAbort(a) === false ) { return; } - normalValue = a; - } - }); - return; - } - const prop = chain.slice(0, pos); - const v = owner[prop]; - chain = chain.slice(pos + 1); - if ( v instanceof safe.Object || typeof v === 'object' && v !== null ) { - trapChain(v, chain); - return; - } - trapProp(owner, prop, true, { - v: undefined, - init: function(v) { - this.v = v; - return true; - }, - getter: function() { - return this.v; - }, - setter: function(a) { - this.v = a; - if ( a instanceof safe.Object ) { - trapChain(a, chain); - } - } - }); - }; - trapChain(window, chain); - } - runAt(( ) => { - setConstant(chain, rawValue); - }, extraArgs.runAt); -}; -functions['setConstant'] = function setConstant( - ...args -) { - setConstantFn(false, ...args); -}; -functions['trustedSetConstant'] = function trustedSetConstant( - ...args -) { - setConstantFn(true, ...args); -}; -functions['ArglistParser'] = class ArglistParser { - constructor(separatorChar = ',', mustQuote = false) { - this.separatorChar = this.actualSeparatorChar = separatorChar; - this.separatorCode = this.actualSeparatorCode = separatorChar.charCodeAt(0); - this.mustQuote = mustQuote; - this.quoteBeg = 0; this.quoteEnd = 0; - this.argBeg = 0; this.argEnd = 0; - this.separatorBeg = 0; this.separatorEnd = 0; - this.transform = false; - this.failed = false; - this.reWhitespaceStart = /^\s+/; - this.reWhitespaceEnd = /(?:^|\S)(\s+)$/; - this.reOddTrailingEscape = /(?:^|[^\\])(?:\\\\)*\\$/; - this.reTrailingEscapeChars = /\\+$/; - } - nextArg(pattern, beg = 0) { - const len = pattern.length; - this.quoteBeg = beg + this.leftWhitespaceCount(pattern.slice(beg)); - this.failed = false; - const qc = pattern.charCodeAt(this.quoteBeg); - if ( qc === 0x22 /* " */ || qc === 0x27 /* ' */ || qc === 0x60 /* ` */ ) { - this.indexOfNextArgSeparator(pattern, qc); - if ( this.argEnd !== len ) { - this.quoteEnd = this.argEnd + 1; - this.separatorBeg = this.separatorEnd = this.quoteEnd; - this.separatorEnd += this.leftWhitespaceCount(pattern.slice(this.quoteEnd)); - if ( this.separatorEnd === len ) { return this; } - if ( pattern.charCodeAt(this.separatorEnd) === this.separatorCode ) { - this.separatorEnd += 1; - return this; - } - } - } - this.indexOfNextArgSeparator(pattern, this.separatorCode); - this.separatorBeg = this.separatorEnd = this.argEnd; - if ( this.separatorBeg < len ) { - this.separatorEnd += 1; - } - this.argEnd -= this.rightWhitespaceCount(pattern.slice(0, this.separatorBeg)); - this.quoteEnd = this.argEnd; - if ( this.mustQuote ) { - this.failed = true; - } - return this; - } - normalizeArg(s, char = '') { - if ( char === '' ) { char = this.actualSeparatorChar; } - let out = ''; - let pos = 0; - while ( (pos = s.lastIndexOf(char)) !== -1 ) { - out = s.slice(pos) + out; - s = s.slice(0, pos); - const match = this.reTrailingEscapeChars.exec(s); - if ( match === null ) { continue; } - const tail = (match[0].length & 1) !== 0 - ? match[0].slice(0, -1) - : match[0]; - out = tail + out; - s = s.slice(0, -match[0].length); - } - if ( out === '' ) { return s; } - return s + out; - } - leftWhitespaceCount(s) { - const match = this.reWhitespaceStart.exec(s); - return match === null ? 0 : match[0].length; - } - rightWhitespaceCount(s) { - const match = this.reWhitespaceEnd.exec(s); - return match === null ? 0 : match[1].length; - } - indexOfNextArgSeparator(pattern, separatorCode) { - this.argBeg = this.argEnd = separatorCode !== this.separatorCode - ? this.quoteBeg + 1 - : this.quoteBeg; - this.transform = false; - if ( separatorCode !== this.actualSeparatorCode ) { - this.actualSeparatorCode = separatorCode; - this.actualSeparatorChar = String.fromCharCode(separatorCode); - } - while ( this.argEnd < pattern.length ) { - const pos = pattern.indexOf(this.actualSeparatorChar, this.argEnd); - if ( pos === -1 ) { - return (this.argEnd = pattern.length); - } - if ( this.reOddTrailingEscape.test(pattern.slice(0, pos)) === false ) { - return (this.argEnd = pos); - } - this.transform = true; - this.argEnd = pos + 1; - } - } -}; -functions['parseReplaceFn'] = function parseReplaceFn(s) { - if ( s.charCodeAt(0) !== 0x2F /* / */ ) { return; } - const parser = new ArglistParser('/'); - parser.nextArg(s, 1); - let pattern = s.slice(parser.argBeg, parser.argEnd); - if ( parser.transform ) { - pattern = parser.normalizeArg(pattern); - } - if ( pattern === '' ) { return; } - parser.nextArg(s, parser.separatorEnd); - let replacement = s.slice(parser.argBeg, parser.argEnd); - if ( parser.separatorEnd === parser.separatorBeg ) { return; } - if ( parser.transform ) { - replacement = parser.normalizeArg(replacement); - } - const flags = s.slice(parser.separatorEnd); - try { - return { re: new RegExp(pattern, flags), replacement }; - } catch { - } -}; -functions['trustedReplaceArgument'] = function trustedReplaceArgument( - propChain = '', - argposRaw = '', - argraw = '', - ...varargs -) { - if ( propChain === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-replace-argument', propChain, argposRaw, argraw); - const argoffset = parseInt(argposRaw, 10) || 0; - const extraArgs = safe.parseVarargs(varargs); - let replacer; - if ( argraw.startsWith('repl:/') ) { - const parsed = parseReplaceFn(argraw.slice(5)); - if ( parsed === undefined ) { return; } - replacer = arg => `${arg}`.replace(replacer.re, replacer.replacement); - Object.assign(replacer, parsed); - } else if ( argraw.startsWith('add:') ) { - const delta = parseFloat(argraw.slice(4)); - if ( isNaN(delta) ) { return; } - replacer = arg => Number(arg) + delta; - } else { - const value = validateConstantFn(true, argraw, extraArgs); - replacer = ( ) => value; - } - const reCondition = extraArgs.condition - ? safe.patternToRegex(`${extraArgs.condition}`) - : /^/; - const getArg = context => { - if ( argposRaw === 'this' ) { return context.thisArg; } - const { callArgs } = context; - const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset; - if ( argpos < 0 || argpos >= callArgs.length ) { return; } - context.private = { argpos }; - return callArgs[argpos]; - }; - const setArg = (context, value) => { - if ( argposRaw === 'this' ) { - if ( value !== context.thisArg ) { - context.thisArg = value; - } - } else if ( context.private ) { - context.callArgs[context.private.argpos] = value; - } - }; - proxyApplyFn(propChain, function(context) { - if ( argposRaw === '' ) { - safe.uboLog(logPrefix, `Arguments:\n${context.callArgs.join('\n')}`); - return context.reflect(); - } - const argBefore = getArg(context); - if ( extraArgs.condition !== undefined ) { - if ( safe.RegExp_test(reCondition, argBefore) === false ) { - return context.reflect(); - } - } - const argAfter = replacer(argBefore); - if ( argAfter !== argBefore ) { - setArg(context, argAfter); - safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`); - } - return context.reflect(); - }); -}; -functions['spoofCSS'] = 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(); - const privatePropToValueMap = new Map(); - for ( let i = 0; i < args.length; i += 2 ) { - const prop = toCamelCase(args[i+0]); - if ( prop === '' ) { break; } - const value = args[i+1]; - if ( typeof value !== 'string' ) { break; } - if ( prop.charCodeAt(0) === 0x5F /* _ */ ) { - privatePropToValueMap.set(prop, value); - } else { - propToValueMap.set(prop, value); - } - } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('spoof-css', selector, ...args); - const instanceProperties = [ 'cssText', 'length', 'parentRule' ]; - const spoofStyle = (prop, real) => { - const normalProp = toCamelCase(prop); - const shouldSpoof = propToValueMap.has(normalProp); - const value = shouldSpoof ? propToValueMap.get(normalProp) : real; - if ( shouldSpoof ) { - safe.uboLog(logPrefix, `Spoofing ${prop} to ${value}`); - } - return value; - }; - const cloackFunc = (fn, thisArg, name) => { - const trap = fn.bind(thisArg); - Object.defineProperty(trap, 'name', { value: name }); - Object.defineProperty(trap, 'toString', { - value: ( ) => `function ${name}() { [native code] }` - }); - return trap; - }; - self.getComputedStyle = new Proxy(self.getComputedStyle, { - apply: function(target, thisArg, args) { - // eslint-disable-next-line no-debugger - if ( privatePropToValueMap.has('_debug') ) { debugger; } - 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) { - if ( typeof target[prop] === 'function' ) { - if ( prop === 'getPropertyValue' ) { - return cloackFunc(function getPropertyValue(prop) { - return spoofStyle(prop, target[prop]); - }, target, 'getPropertyValue'); - } - return cloackFunc(target[prop], target, prop); - } - if ( instanceProperties.includes(prop) ) { - return Reflect.get(target, prop); - } - return spoofStyle(prop, Reflect.get(target, prop)); - }, - getOwnPropertyDescriptor(target, prop) { - if ( propToValueMap.has(prop) ) { - return { - configurable: true, - enumerable: true, - value: propToValueMap.get(prop), - writable: true, - }; - } - return Reflect.getOwnPropertyDescriptor(target, prop); - }, - }); - return proxiedStyle; - }, - get(target, prop) { - if ( prop === 'toString' ) { - return target.toString.bind(target); - } - return Reflect.get(target, prop); - }, - }); - Element.prototype.getBoundingClientRect = new Proxy(Element.prototype.getBoundingClientRect, { - apply: function(target, thisArg, args) { - // eslint-disable-next-line no-debugger - if ( privatePropToValueMap.has('_debug') ) { debugger; } - const rect = Reflect.apply(target, thisArg, args); - const targetElements = new WeakSet(document.querySelectorAll(selector)); - if ( targetElements.has(thisArg) === false ) { return rect; } - let { x, y, height, width } = rect; - if ( privatePropToValueMap.has('_rectx') ) { - x = parseFloat(privatePropToValueMap.get('_rectx')); - } - if ( privatePropToValueMap.has('_recty') ) { - y = parseFloat(privatePropToValueMap.get('_recty')); - } - if ( privatePropToValueMap.has('_rectw') ) { - width = parseFloat(privatePropToValueMap.get('_rectw')); - } else if ( propToValueMap.has('width') ) { - width = parseFloat(propToValueMap.get('width')); - } - if ( privatePropToValueMap.has('_recth') ) { - height = parseFloat(privatePropToValueMap.get('_recth')); - } else if ( propToValueMap.has('height') ) { - height = parseFloat(propToValueMap.get('height')); - } - return new self.DOMRect(x, y, width, height); - }, - get(target, prop) { - if ( prop === 'toString' ) { - return target.toString.bind(target); - } - return Reflect.get(target, prop); - }, - }); -}; -functions['preventXhrFn'] = function preventXhrFn( - trusted = false, - propsToMatch = '', - directive = '' -) { - if ( typeof propsToMatch !== 'string' ) { return; } - const safe = safeSelf(); - const scriptletName = trusted ? 'trusted-prevent-xhr' : 'prevent-xhr'; - const logPrefix = safe.makeLogPrefix(scriptletName, propsToMatch, directive); - const xhrInstances = new WeakMap(); - const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url'); - const warOrigin = scriptletGlobals.warOrigin; - const safeDispatchEvent = (xhr, type) => { - try { - xhr.dispatchEvent(new Event(type)); - } catch { - } - }; - proxyApplyFn('XMLHttpRequest.prototype.open', function(context) { - const { thisArg, callArgs } = context; - xhrInstances.delete(thisArg); - const [ method, url, ...args ] = callArgs; - if ( warOrigin !== undefined && url.startsWith(warOrigin) ) { - return context.reflect(); - } - const haystack = { method, url }; - if ( propsToMatch === '' && directive === '' ) { - safe.uboLog(logPrefix, `Called: ${safe.JSON_stringify(haystack, null, 2)}`); - return context.reflect(); - } - if ( matchObjectPropertiesFn(propNeedles, haystack) ) { - const xhrDetails = Object.assign(haystack, { - xhr: thisArg, - defer: args.length === 0 || !!args[0], - directive, - headers: { - 'date': '', - 'content-type': '', - 'content-length': '', - }, - url: haystack.url, - props: { - response: { value: '' }, - responseText: { value: '' }, - responseXML: { value: null }, - }, - }); - xhrInstances.set(thisArg, xhrDetails); - } - return context.reflect(); - }); - proxyApplyFn('XMLHttpRequest.prototype.send', function(context) { - const { thisArg } = context; - const xhrDetails = xhrInstances.get(thisArg); - if ( xhrDetails === undefined ) { - return context.reflect(); - } - xhrDetails.headers['date'] = (new Date()).toUTCString(); - let xhrText = ''; - switch ( thisArg.responseType ) { - case 'arraybuffer': - xhrDetails.props.response.value = new ArrayBuffer(0); - xhrDetails.headers['content-type'] = 'application/octet-stream'; - break; - case 'blob': - xhrDetails.props.response.value = new Blob([]); - xhrDetails.headers['content-type'] = 'application/octet-stream'; - break; - case 'document': { - const parser = new DOMParser(); - const doc = parser.parseFromString('', 'text/html'); - xhrDetails.props.response.value = doc; - xhrDetails.props.responseXML.value = doc; - xhrDetails.headers['content-type'] = 'text/html'; - break; - } - case 'json': - xhrDetails.props.response.value = {}; - xhrDetails.props.responseText.value = '{}'; - xhrDetails.headers['content-type'] = 'application/json'; - break; - default: { - if ( directive === '' ) { break; } - xhrText = generateContentFn(trusted, xhrDetails.directive); - if ( xhrText instanceof Promise ) { - xhrText = xhrText.then(text => { - xhrDetails.props.response.value = text; - xhrDetails.props.responseText.value = text; - }); - } else { - xhrDetails.props.response.value = xhrText; - xhrDetails.props.responseText.value = xhrText; - } - xhrDetails.headers['content-type'] = 'text/plain'; - break; - } - } - if ( xhrDetails.defer === false ) { - xhrDetails.headers['content-length'] = `${xhrDetails.props.response.value}`.length; - Object.defineProperties(xhrDetails.xhr, { - readyState: { value: 4 }, - responseURL: { value: xhrDetails.url }, - status: { value: 200 }, - statusText: { value: 'OK' }, - }); - Object.defineProperties(xhrDetails.xhr, xhrDetails.props); - return; - } - Promise.resolve(xhrText).then(( ) => xhrDetails).then(details => { - Object.defineProperties(details.xhr, { - readyState: { value: 1, configurable: true }, - responseURL: { value: xhrDetails.url }, - }); - safeDispatchEvent(details.xhr, 'readystatechange'); - return details; - }).then(details => { - xhrDetails.headers['content-length'] = `${details.props.response.value}`.length; - Object.defineProperties(details.xhr, { - readyState: { value: 2, configurable: true }, - status: { value: 200 }, - statusText: { value: 'OK' }, - }); - safeDispatchEvent(details.xhr, 'readystatechange'); - return details; - }).then(details => { - Object.defineProperties(details.xhr, { - readyState: { value: 3, configurable: true }, - }); - Object.defineProperties(details.xhr, details.props); - safeDispatchEvent(details.xhr, 'readystatechange'); - return details; - }).then(details => { - Object.defineProperties(details.xhr, { - readyState: { value: 4 }, - }); - safeDispatchEvent(details.xhr, 'readystatechange'); - safeDispatchEvent(details.xhr, 'load'); - safeDispatchEvent(details.xhr, 'loadend'); - safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`); - }); - }); - proxyApplyFn('XMLHttpRequest.prototype.getResponseHeader', function(context) { - const { thisArg } = context; - const xhrDetails = xhrInstances.get(thisArg); - if ( xhrDetails === undefined || thisArg.readyState < thisArg.HEADERS_RECEIVED ) { - return context.reflect(); - } - const headerName = `${context.callArgs[0]}`; - const value = xhrDetails.headers[headerName.toLowerCase()]; - if ( value !== undefined && value !== '' ) { return value; } - return null; - }); - proxyApplyFn('XMLHttpRequest.prototype.getAllResponseHeaders', function(context) { - const { thisArg } = context; - const xhrDetails = xhrInstances.get(thisArg); - if ( xhrDetails === undefined || thisArg.readyState < thisArg.HEADERS_RECEIVED ) { - return context.reflect(); - } - const out = []; - for ( const [ name, value ] of Object.entries(xhrDetails.headers) ) { - if ( !value ) { continue; } - out.push(`${name}: ${value}`); - } - if ( out.length !== 0 ) { out.push(''); } - return out.join('\r\n'); - }); -}; -functions['preventXhr'] = function preventXhr(...args) { - preventXhrFn(false, ...args); -}; -functions['trustedPreventXhr'] = function trustedPreventXhr(...args) { - preventXhrFn(true, ...args); -}; -functions['proxyToStringFn'] = function proxyToStringFn(proxiedFn, nativeFn) { - if ( proxyToStringFn.proxies === undefined ) { - proxyToStringFn.proxies = new WeakMap(); - proxyToStringFn.nativeToString = Function.prototype.toString; - const proxiedToString = new Proxy(Function.prototype.toString, { - apply(target, thisArg) { - let proxied = thisArg; - for(;;) { - const fn = proxyToStringFn.proxies.get(proxied); - if ( fn === undefined ) { break; } - proxied = fn; - } - return proxyToStringFn.nativeToString.call(proxied); - } - }); - proxyToStringFn.proxies.set(proxiedToString, proxyToStringFn.nativeToString); - Function.prototype.toString = proxiedToString; - } - proxyToStringFn.proxies.set(proxiedFn, nativeFn); -}; -functions['modifyXhrResponseFn'] = function modifyXhrResponseFn( - propsToMatch = '', - modifierFn = '' -) { - if ( typeof propsToMatch !== 'string' ) { return; } - const safe = safeSelf(); - if ( modifyXhrResponseFn.xhrInstances === undefined ) { - modifyXhrResponseFn.xhrInstances = new WeakMap(); - } - const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url'); - const NativeXMLHttpRequest = self.XMLHttpRequest; - const TrappedXMLHttpRequest = class XMLHttpRequest extends NativeXMLHttpRequest { - open(method, url, ...args) { - const haystack = { method, url }; - if ( propsToMatch === '' ) { - safe.uboLog(`modifyXhrResponseFn() / Called: ${safe.JSON_stringify(haystack, null, 2)}`); - } else if ( matchObjectPropertiesFn(propNeedles, haystack) ) { - modifyXhrResponseFn.xhrInstances.set(this, modifierFn); - } - return super.open(method, url, ...args); - } - get response() { - const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); - return modifierFn - ? modifierFn(this, super.response) - : super.response; - } - get responseText() { - const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); - return modifierFn - ? modifierFn(this, super.responseText) - : super.responseText; - } - get responseXML() { - const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); - return modifierFn - ? modifierFn(this, super.responseXML) - : super.responseXML; - } - }; - proxyToStringFn(TrappedXMLHttpRequest.prototype.open, NativeXMLHttpRequest.prototype.open); - proxyToStringFn(TrappedXMLHttpRequest, NativeXMLHttpRequest); - self.XMLHttpRequest = TrappedXMLHttpRequest; -}; -functions['mpegdashPrune'] = function mpegdashPrune( - selector = '', - propsToMatch = '' -) { - if ( typeof selector !== 'string' ) { return; } - if ( selector === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('mpegdash-prune', selector, propsToMatch); - const queryAll = (xmlDoc, selector) => { - if ( selector.startsWith('xpath:') === false ) { - return Array.from(xmlDoc.querySelectorAll(selector)); - } - const xpr = xmlDoc.evaluate( - selector.slice(6), - 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; - }; - const rePTparse = /^PT(\d+D)?(\d+H)?(\d+M)?([\d.]+S)?$/; - const secondsPerDay = 24 * 60 * 60; - const secondsPerHour = 60 * 60; - const secondsPerMinute = 60; - const secondsFromPT = pt => { - const match = rePTparse.exec(pt); - if ( match === null ) { return; } - let seconds = 0; - if ( match[1] ) { - const d = parseFloat(match[1].slice(0, -1)); - if ( isNaN(d) ) { return; } - seconds += d * secondsPerDay; - } - if ( match[2] ) { - const h = parseFloat(match[2].slice(0, -1)); - if ( isNaN(h) ) { return; } - seconds += h * secondsPerHour; - } - if ( match[3] ) { - const m = parseFloat(match[3].slice(0, -1)); - if ( isNaN(m) ) { return; } - seconds += m * secondsPerMinute; - } - if ( match[4] ) { - const s = parseFloat(match[4].slice(0, -1)); - if ( isNaN(s) ) { return; } - seconds += s; - } - return seconds; - }; - const ptFromSeconds = seconds => { - const parts = [ 'PT' ]; - const d = Math.floor(seconds / secondsPerDay); - if ( d ) { - parts.push(`${d}D`); - seconds -= d * secondsPerDay; - } - const h = Math.floor(seconds / secondsPerHour); - if ( h ) { - parts.push(`${h}H`); - seconds -= h * secondsPerHour; - } - const m = Math.floor(seconds / secondsPerMinute); - if ( m ) { - parts.push(`${m}M`); - seconds -= m * secondsPerMinute; - } - parts.push(`${seconds}S`); - return parts.join(''); - }; - const fixTimeAttributes = xmlDoc => { - try { - const periods = queryAll(xmlDoc, 'MPD > Period'); - if ( periods.length === 0 ) { return; } - let seconds = 0; - for ( const period of periods ) { - const startAttrBefore = period.getAttribute('start'); - const durAttr = period.getAttribute('duration'); - if ( startAttrBefore === null || durAttr === null ) { continue; } - const startAttrAfter = ptFromSeconds(seconds); - period.setAttribute('start', startAttrAfter); - if ( period.hasAttribute('id') ) { - const idAttr = period.getAttribute('id'); - period.setAttribute('id', idAttr.replace(startAttrBefore, startAttrAfter)); - } - seconds += secondsFromPT(durAttr); - } - const mpds = queryAll(xmlDoc, 'MPD[mediaPresentationDuration]'); - if ( mpds.length !== 1 ) { return; } - mpds[0].setAttribute('mediaPresentationDuration', ptFromSeconds(seconds)); - } catch { - } - }; - const pruneFromDoc = xmlDoc => { - try { - if ( selector === '' ) { - const serializer = new XMLSerializer(); - safe.uboLog(logPrefix, `Document is\n\t${serializer.serializeToString(xmlDoc)}`); - } - const items = queryAll(xmlDoc, selector); - if ( items.length === 0 ) { return xmlDoc; } - safe.uboLog(logPrefix, `Patching ${items.length} items`); - for ( const item of items ) { - if ( item.nodeType !== 1 ) { continue; } - item.setAttribute('duration', 'PT0S'); - } - fixTimeAttributes(xmlDoc); - } catch(ex) { - safe.uboErr(logPrefix, `Error: ${ex}`); - } - return xmlDoc; - }; - const pruneFromText = text => { - if ( (/^\s*\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 { - } - return text; - }; - modifyXhrResponseFn(propsToMatch, (xhr, before) => { - if ( before instanceof XMLDocument ) { - return pruneFromDoc(before); - } - if ( typeof before === 'string' ) { - return pruneFromText(before); - } - return before; - }); -}; -functions['getCookieFn'] = function getCookieFn( - name = '' -) { - const safe = safeSelf(); - for ( const s of safe.String_split.call(document.cookie, /\s*;\s*/) ) { - const pos = s.indexOf('='); - if ( pos === -1 ) { continue; } - if ( s.slice(0, pos) !== name ) { continue; } - return s.slice(pos+1).trim(); - } -}; -functions['setCookieFn'] = function setCookieFn( - trusted = false, - name = '', - value = '', - expires = '', - path = '', - options = {}, -) { - // 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 - // The characters [",] are given a pass from the RFC requirements because - // apparently browsers do not follow the RFC to the letter. - if ( /[^ -:<-[\]-~]/.test(value) ) { - value = encodeURIComponent(value); - } - - const cookieBefore = getCookieFn(name); - if ( cookieBefore !== undefined && options.dontOverwrite ) { return; } - if ( cookieBefore === value && options.reload ) { return; } - - const cookieParts = [ name, '=', value ]; - if ( expires !== '' ) { - cookieParts.push('; expires=', expires); - } - - if ( path === '' ) { path = '/'; } - else if ( path === 'none' ) { path = ''; } - if ( path !== '' && path !== '/' ) { return; } - if ( path === '/' ) { - cookieParts.push('; path=/'); - } - - if ( trusted ) { - if ( options.domain ) { - let domain = options.domain; - if ( /^\/.+\//.test(domain) ) { - const baseURL = new URL(document.baseURI); - const reDomain = new RegExp(domain.slice(1, -1)); - const match = reDomain.exec(baseURL.hostname); - domain = match ? match[0] : undefined; - } - if ( domain ) { - cookieParts.push(`; domain=${domain}`); - } - } - cookieParts.push('; Secure'); - } else if ( /^__(Host|Secure)-/.test(name) ) { - cookieParts.push('; Secure'); - } - - try { - document.cookie = cookieParts.join(''); - } catch { - } - - const done = getCookieFn(name) === value; - if ( done && options.reload ) { - window.location.reload(); - } - - return done; -}; -functions['getSafeCookieValuesFn'] = function getSafeCookieValuesFn() { - return [ - 'accept', 'reject', - 'accepted', 'rejected', 'notaccepted', - 'allow', 'disallow', 'deny', - 'allowed', 'denied', - 'approved', 'disapproved', - 'checked', 'unchecked', - 'dismiss', 'dismissed', - 'enable', 'disable', - 'enabled', 'disabled', - 'essential', 'nonessential', - 'forbidden', 'forever', - 'hide', 'hidden', - 'necessary', 'required', - 'ok', - 'on', 'off', - 'true', 't', 'false', 'f', - 'yes', 'y', 'no', 'n', - 'all', 'none', 'functional', - 'granted', 'done', - 'decline', 'declined', - 'closed', 'next', 'mandatory', - 'disagree', 'agree', - 'set', 'unset', - 'given', - ]; -}; -functions['setCookie'] = function setCookie( - name = '', - value = '', - path = '', - ...varargs -) { - if ( name === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path); - const normalized = value.toLowerCase(); - const match = /^("?)(.+)\1$/.exec(normalized); - const unquoted = match && match[2] || normalized; - const validValues = getSafeCookieValuesFn(); - if ( validValues.includes(unquoted) === false ) { - if ( /^-?\d+$/.test(unquoted) === false ) { return; } - const n = parseInt(value, 10) || 0; - if ( n < -32767 || n > 32767 ) { return; } - } - - const done = setCookieFn( - false, - name, - value, - '', - path, - safe.parseVarargs(varargs) - ); - - if ( done ) { - safe.uboLog(logPrefix, 'Done'); - } -}; -functions['setCookieReload'] = function setCookieReload(name, value, path, ...args) { - setCookie(name, value, path, 'reload', '1', ...args); -}; -functions['trustedSetCookie'] = function trustedSetCookie( - name = '', - value = '', - offsetExpiresSec = '', - path = '', - ...varargs -) { - if ( name === '' ) { return; } - - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path); - const time = new Date(); - - if ( value.includes('$now$') ) { - value = value.replaceAll('$now$', time.getTime()); - } - if ( value.includes('$currentDate$') ) { - value = value.replaceAll('$currentDate$', time.toUTCString()); - } - if ( value.includes('$currentISODate$') ) { - value = value.replaceAll('$currentISODate$', time.toISOString()); - } - - let expires = ''; - 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)); - } - expires = time.toUTCString(); - } - - const done = setCookieFn( - true, - name, - value, - expires, - path, - safe.parseVarargs(varargs) - ); - - if ( done ) { - safe.uboLog(logPrefix, 'Done'); - } -}; -functions['trustedSetCookieReload'] = function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) { - trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args); -}; -functions['removeCookie'] = function removeCookie( - needle = '', - ...varargs -) { - if ( typeof needle !== 'string' ) { return; } - const safe = safeSelf(); - const reName = safe.patternToRegex(needle); - const extraArgs = safe.parseVarargs(varargs); - const throttle = (fn, ms = 500) => { - if ( throttle.timer !== undefined ) { return; } - throttle.timer = setTimeout(( ) => { - throttle.timer = undefined; - fn(); - }, ms); - }; - const baseURL = new URL(document.baseURI); - let targetDomain = extraArgs.domain; - if ( targetDomain && /^\/.+\//.test(targetDomain) ) { - const reDomain = new RegExp(targetDomain.slice(1, -1)); - const match = reDomain.exec(baseURL.hostname); - targetDomain = match ? match[0] : undefined; - } - const remove = ( ) => { - safe.String_split.call(document.cookie, ';').forEach(cookieStr => { - const pos = cookieStr.indexOf('='); - if ( pos === -1 ) { return; } - const cookieName = cookieStr.slice(0, pos).trim(); - if ( reName.test(cookieName) === false ) { return; } - const part1 = cookieName + '='; - const part2a = `; domain=${baseURL.hostname}`; - const part2b = `; domain=.${baseURL.hostname}`; - let part2c, part2d; - if ( targetDomain ) { - part2c = `; domain=${targetDomain}`; - part2d = `; domain=.${targetDomain}`; - } else if ( document.domain ) { - const domain = document.domain; - if ( domain !== baseURL.hostname ) { - part2c = `; domain=.${domain}`; - } - if ( domain.startsWith('www.') ) { - part2d = `; domain=${domain.replace('www', '')}`; - } - } - const part3 = '; path=/'; - const part4 = '; Max-Age=-1000; expires=Thu, 01 Jan 1970 00:00:00 GMT'; - 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; - } - if ( part2d !== undefined ) { - document.cookie = part1 + part2d + part3 + part4; - } - }); - }; - remove(); - window.addEventListener('beforeunload', remove); - if ( typeof extraArgs.when !== 'string' ) { return; } - const supportedEventTypes = [ 'scroll', 'keydown' ]; - const eventTypes = safe.String_split.call(extraArgs.when, /\s/); - for ( const type of eventTypes ) { - if ( supportedEventTypes.includes(type) === false ) { continue; } - document.addEventListener(type, ( ) => { - throttle(remove); - }, { passive: true }); - } -}; -functions['setLocalStorageItemFn'] = function setLocalStorageItemFn( - which = 'local', - trusted = false, - key = '', - value = '', - options = {} -) { - if ( key === '' ) { return; } - - // For increased compatibility with AdGuard - if ( value === 'emptyArr' ) { - value = '[]'; - } else if ( value === 'emptyObj' ) { - value = '{}'; - } - - const trustedValues = [ - '', - 'undefined', 'null', - '{}', '[]', '""', - '$remove$', - ...getSafeCookieValuesFn(), - ]; - - if ( trusted ) { - if ( value.includes('$now$') ) { - value = value.replaceAll('$now$', Date.now()); - } - if ( value.includes('$currentDate$') ) { - value = value.replaceAll('$currentDate$', `${Date()}`); - } - if ( value.includes('$currentISODate$') ) { - value = value.replaceAll('$currentISODate$', (new Date()).toISOString()); - } - } else { - 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; } - const n = parseInt(unquoted, 10) || 0; - if ( n < -32767 || n > 32767 ) { return; } - } - } - - let modified = false; - - try { - const storage = self[`${which}Storage`]; - if ( value === '$remove$' ) { - 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); } - } - modified = toRemove.length !== 0; - for ( const key of toRemove ) { - storage.removeItem(key); - } - } else { - - const before = storage.getItem(key); - const after = `${value}`; - modified = after !== before; - if ( modified ) { - storage.setItem(key, after); - } - } - } catch { - } - - if ( modified && typeof options.reload === 'number' ) { - setTimeout(( ) => { window.location.reload(); }, options.reload); - } -}; -functions['setLocalStorageItem'] = function setLocalStorageItem(key = '', value = '', ...varargs) { - const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('local', false, key, value, options); -}; -functions['setSessionStorageItem'] = function setSessionStorageItem(key = '', value = '', ...varargs) { - const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('session', false, key, value, options); -}; -functions['trustedSetLocalStorageItem'] = function trustedSetLocalStorageItem(key = '', value = '', ...varargs) { - const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('local', true, key, value, options); -}; -functions['trustedSetSessionStorageItem'] = function trustedSetSessionStorageItem(key = '', value = '', ...varargs) { - const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('session', true, key, value, options); -}; -functions['abortOnPropertyRead'] = function abortOnPropertyRead( - chain = '' -) { - if ( typeof chain !== 'string' ) { return; } - if ( chain === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('abort-on-property-read', chain); - const exceptionToken = getExceptionTokenFn(); - const abort = function() { - safe.uboLog(logPrefix, 'Aborted'); - throw new ReferenceError(exceptionToken); - }; - 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]; - 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); -}; -functions['abortOnPropertyWrite'] = function abortOnPropertyWrite( - prop = '' -) { - if ( typeof prop !== 'string' ) { return; } - if ( prop === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('abort-on-property-write', prop); - const exceptionToken = getExceptionTokenFn(); - 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() { - safe.uboLog(logPrefix, 'Aborted'); - throw new ReferenceError(exceptionToken); - } - }); -}; -functions['adjustSetInterval'] = function adjustSetInterval( - needleArg = '', - delayArg = '', - boostArg = '' -) { - if ( typeof needleArg !== 'string' ) { return; } - const safe = safeSelf(); - const reNeedle = safe.patternToRegex(needleArg); - 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) - ? Math.min(Math.max(boost, 0.001), 50) - : 0.05; - self.setInterval = new Proxy(self.setInterval, { - apply: function(target, thisArg, args) { - const [ a, b ] = args; - if ( - (delay === -1 || b === delay) && - reNeedle.test(a.toString()) - ) { - args[1] = b * boost; - } - return target.apply(thisArg, args); - } - }); -}; -functions['adjustSetTimeout'] = function adjustSetTimeout( - needleArg = '', - delayArg = '', - boostArg = '' -) { - if ( typeof needleArg !== 'string' ) { return; } - const safe = safeSelf(); - const reNeedle = safe.patternToRegex(needleArg); - 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) - ? Math.min(Math.max(boost, 0.001), 50) - : 0.05; - self.setTimeout = new Proxy(self.setTimeout, { - apply: function(target, thisArg, args) { - const [ a, b ] = args; - if ( - (delay === -1 || b === delay) && - reNeedle.test(a.toString()) - ) { - args[1] = b * boost; - } - return target.apply(thisArg, args); - } - }); -}; -functions['preventRefresh'] = function preventRefresh( - delay = '' -) { - if ( typeof delay !== 'string' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('prevent-refresh', delay); - const stop = content => { - window.stop(); - safe.uboLog(logPrefix, `Prevented "${content}"`); - }; - const defuse = ( ) => { - const meta = document.querySelector('meta[http-equiv="refresh" i][content]'); - if ( meta === null ) { return; } - const content = meta.getAttribute('content') || ''; - const ms = delay === '' - ? Math.max(parseFloat(content) || 0, 0) * 500 - : 0; - if ( ms === 0 ) { - stop(content); - } else { - setTimeout(( ) => { stop(content); }, ms); - } - }; - self.addEventListener('load', defuse, { capture: true, once: true }); -}; -functions['removeClass'] = function removeClass( - rawToken = '', - rawSelector = '', - behavior = '' -) { - if ( typeof rawToken !== 'string' ) { return; } - if ( rawToken === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('remove-class', rawToken, rawSelector, behavior); - const tokens = safe.String_split.call(rawToken, /\s*\|\s*/); - const selector = tokens - .map(a => `${rawSelector}.${CSS.escape(a)}`) - .join(','); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Target selector:\n\t${selector}`); - } - const mustStay = /\bstay\b/.test(behavior); - let timer; - const rmclass = ( ) => { - timer = undefined; - try { - const nodes = document.querySelectorAll(selector); - for ( const node of nodes ) { - node.classList.remove(...tokens); - safe.uboLog(logPrefix, 'Removed class(es)'); - } - } catch { - } - if ( mustStay ) { return; } - if ( document.readyState !== 'complete' ) { return; } - observer.disconnect(); - }; - 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 = onIdleFn(rmclass, { timeout: 67 }); - }; - const observer = new MutationObserver(mutationHandler); - const start = ( ) => { - rmclass(); - observer.observe(document, { - attributes: true, - attributeFilter: [ 'class' ], - childList: true, - subtree: true, - }); - }; - runAt(( ) => { - start(); - }, /\bcomplete\b/.test(behavior) ? 'idle' : 'loading'); -}; -functions['webrtcIf'] = function webrtcIf( - good = '' -) { - if ( typeof good !== 'string' ) { return; } - const safe = safeSelf(); - const reGood = safe.patternToRegex(good); - 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 ) { - log('uBO:', args[1]); - return Reflect.apply(target, thisArg, args.slice(0, 1)); - } - return Reflect.apply(target, thisArg, args); - }, - }); - window[rtcName] = - new Proxy(peerConnectionCtor, { - construct: function(target, args) { - if ( isGoodConfig(target, args[0]) === false ) { - log('uBO:', args[0]); - return Reflect.construct(target); - } - return Reflect.construct(target, args); - } - }); -}; -functions['noWindowOpenIf'] = function noWindowOpenIf( - pattern = '', - delay = '', - decoy = '' -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('no-window-open-if', pattern, delay, decoy); - const targetMatchResult = pattern.startsWith('!') === false; - if ( targetMatchResult === false ) { - pattern = pattern.slice(1); - } - const rePattern = safe.patternToRegex(pattern); - const autoRemoveAfter = (parseFloat(delay) || 0) * 1000; - const setTimeout = self.setTimeout; - 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); - return decoyElem; - }; - const noopFunc = function(){}; - proxyApplyFn('open', function open(context) { - if ( pattern === 'debug' && safe.logLevel !== 0 ) { - debugger; // eslint-disable-line no-debugger - return context.reflect(); - } - const { callArgs } = context; - const haystack = callArgs.join(' '); - if ( rePattern.test(haystack) !== targetMatchResult ) { - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Allowed (${callArgs.join(', ')})`); - } - return context.reflect(); - } - safe.uboLog(logPrefix, `Prevented (${callArgs.join(', ')})`); - if ( delay === '' ) { return null; } - if ( decoy === 'blank' ) { - callArgs[0] = 'about:blank'; - const r = context.reflect(); - setTimeout(( ) => { r.close(); }, autoRemoveAfter); - return r; - } - const decoyElem = decoy === 'obj' - ? createDecoy('object', 'data', ...callArgs) - : createDecoy('iframe', 'src', ...callArgs); - let popup = decoyElem.contentWindow; - if ( typeof popup === 'object' && popup !== null ) { - Object.defineProperty(popup, 'closed', { value: false }); - } else { - popup = new Proxy(self, { - get: function(target, prop, ...args) { - if ( prop === 'closed' ) { return false; } - const r = Reflect.get(target, prop, ...args); - if ( typeof r === 'function' ) { return noopFunc; } - return r; - }, - set: function(...args) { - return Reflect.set(...args); - }, - }); - } - if ( safe.logLevel !== 0 ) { - popup = new Proxy(popup, { - get: function(target, prop, ...args) { - const r = Reflect.get(target, prop, ...args); - safe.uboLog(logPrefix, `popup / get ${prop} === ${r}`); - if ( typeof r === 'function' ) { - return (...args) => { return r.call(target, ...args); }; - } - return r; - }, - set: function(target, prop, value, ...args) { - safe.uboLog(logPrefix, `popup / set ${prop} = ${value}`); - return Reflect.set(target, prop, value, ...args); - }, - }); - } - return popup; - }); -}; -functions['closeWindow'] = function closeWindow( - arg1 = '' -) { - if ( typeof arg1 !== 'string' ) { return; } - const safe = safeSelf(); - let subject = ''; - if ( /^\/.*\/$/.test(arg1) ) { - subject = window.location.href; - } else if ( arg1 !== '' ) { - subject = `${window.location.pathname}${window.location.search}`; - } - try { - const re = safe.patternToRegex(arg1); - if ( re.test(subject) ) { - window.close(); - } - } catch(ex) { - console.log(ex); - } -}; -functions['windowNameDefuser'] = function windowNameDefuser() { - if ( window === window.top ) { - window.name = ''; - } -}; -functions['overlayBuster'] = function overlayBuster(allFrames) { - if ( allFrames === '' && window !== window.top ) { return; } - 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(); - } -}; -functions['alertBuster'] = function alertBuster() { - window.alert = new Proxy(window.alert, { - apply: function(a) { - console.info(a); - }, - get(target, prop) { - if ( prop === 'toString' ) { - return target.toString.bind(target); - } - return Reflect.get(target, prop); - }, - }); -}; -functions['noWebrtc'] = function noWebrtc() { - 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); - } -}; -functions['disableNewtabLinks'] = function disableNewtabLinks() { - document.addEventListener('click', ev => { - let target = ev.target; - while ( target !== null ) { - if ( target.localName === 'a' && target.hasAttribute('target') ) { - ev.stopPropagation(); - ev.preventDefault(); - break; - } - target = target.parentNode; - } - }, { capture: true }); -}; -functions['xmlPrune'] = function xmlPrune( - selector = '', - selectorCheck = '', - urlPattern = '', - ...varargs -) { - if ( typeof selector !== 'string' ) { return; } - if ( selector === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('xml-prune', selector, selectorCheck, urlPattern); - const reUrl = safe.patternToRegex(urlPattern); - const extraArgs = safe.parseVarargs(varargs); - 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; - }; - const pruneFromDoc = xmlDoc => { - try { - if ( selectorCheck !== '' && xmlDoc.querySelector(selectorCheck) === null ) { - return xmlDoc; - } - if ( extraArgs.logdoc ) { - const serializer = new XMLSerializer(); - safe.uboLog(logPrefix, `Document is\n\t${serializer.serializeToString(xmlDoc)}`); - } - const items = queryAll(xmlDoc, selector); - if ( items.length === 0 ) { return xmlDoc; } - safe.uboLog(logPrefix, `Removing ${items.length} items`); - for ( const item of items ) { - if ( item.nodeType === 1 ) { - item.remove(); - } else if ( item.nodeType === 2 ) { - item.ownerElement.removeAttribute(item.nodeName); - } - safe.uboLog(logPrefix, `${item.constructor.name}.${item.nodeName} removed`); - } - } catch(ex) { - safe.uboErr(logPrefix, `Error: ${ex}`); - } - return xmlDoc; - }; - const pruneFromText = text => { - if ( (/^\s*\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 { - } - return text; - }; - const urlFromArg = arg => { - if ( typeof arg === 'string' ) { return arg; } - if ( arg instanceof Request ) { return arg.url; } - return String(arg); - }; - self.fetch = new Proxy(self.fetch, { - apply: function(target, thisArg, args) { - const fetchPromise = Reflect.apply(target, thisArg, args); - if ( reUrl.test(urlFromArg(args[0])) === false ) { - 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 - ); - }); - } - }); - modifyXhrResponseFn(urlPattern, (xhr, before) => { - if ( before instanceof XMLDocument ) { - return pruneFromDoc(before); - } - if ( typeof before === 'string' ) { - return pruneFromText(before); - } - return before; - }); -}; -functions['m3uPrune'] = function m3uPrune( - m3uPattern = '', - urlPattern = '' -) { - if ( typeof m3uPattern !== 'string' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('m3u-prune', m3uPattern, urlPattern); - const toLog = []; - const regexFromArg = arg => { - if ( arg === '' ) { return /^/; } - const match = /^\/(.+)\/([gms]*)$/.exec(arg); - if ( match !== null ) { - let flags = match[2] || ''; - if ( flags.includes('m') ) { flags += 's'; } - return new RegExp(match[1], flags); - } - return new RegExp( - arg.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*+/g, '.*?') - ); - }; - const reM3u = regexFromArg(m3uPattern); - const reUrl = regexFromArg(urlPattern); - const pruneSpliceoutBlock = (lines, i) => { - if ( lines[i].startsWith('#EXT-X-CUE:TYPE="SpliceOut"') === false ) { - return false; - } - toLog.push(`\t${lines[i]}`); - lines[i] = undefined; i += 1; - if ( lines[i].startsWith('#EXT-X-ASSET:CAID') ) { - toLog.push(`\t${lines[i]}`); - lines[i] = undefined; i += 1; - } - if ( lines[i].startsWith('#EXT-X-SCTE35:') ) { - toLog.push(`\t${lines[i]}`); - lines[i] = undefined; i += 1; - } - if ( lines[i].startsWith('#EXT-X-CUE-IN') ) { - toLog.push(`\t${lines[i]}`); - lines[i] = undefined; i += 1; - } - if ( lines[i].startsWith('#EXT-X-SCTE35:') ) { - toLog.push(`\t${lines[i]}`); - 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; } - toLog.push('Discarding', `\t${lines[i]}, \t${lines[i+1]}`); - lines[i] = lines[i+1] = undefined; i += 2; - if ( lines[i].startsWith('#EXT-X-DISCONTINUITY') ) { - toLog.push(`\t${lines[i]}`); - lines[i] = undefined; i += 1; - } - return true; - }; - const pruner = text => { - if ( (/^\s*#EXTM3U/.test(text)) === false ) { return text; } - if ( m3uPattern === '' ) { - safe.uboLog(` Content:\n${text}`); - return text; - } - if ( reM3u.multiline ) { - reM3u.lastIndex = 0; - for (;;) { - const match = reM3u.exec(text); - if ( match === null ) { break; } - 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]; - } - } - text = before.trim() + '\n' + after.trim(); - reM3u.lastIndex = before.length + 1; - toLog.push('Discarding', ...safe.String_split.call(discard, /\n+/).map(s => `\t${s}`)); - if ( reM3u.global === false ) { break; } - } - return text; - } - const lines = safe.String_split.call(text, /\n\r|\n|\r/); - for ( let i = 0; i < lines.length; i++ ) { - if ( lines[i] === undefined ) { continue; } - if ( pruneSpliceoutBlock(lines, i) ) { continue; } - if ( pruneInfBlock(lines, i) ) { continue; } - } - return lines.filter(l => l !== undefined).join('\n'); - }; - const urlFromArg = arg => { - if ( typeof arg === 'string' ) { return arg; } - if ( arg instanceof Request ) { return arg.url; } - return String(arg); - }; - proxyApplyFn('fetch', async function fetch(context) { - const args = context.callArgs; - const fetchPromise = context.reflect(); - if ( reUrl.test(urlFromArg(args[0])) === false ) { return fetchPromise; } - const responseBefore = await fetchPromise; - const responseClone = responseBefore.clone(); - const textBefore = await responseClone.text(); - const textAfter = pruner(textBefore); - if ( textAfter === textBefore ) { return responseBefore; } - const responseAfter = new Response(textAfter, { - status: responseBefore.status, - statusText: responseBefore.statusText, - headers: responseBefore.headers, - }); - Object.defineProperties(responseAfter, { - url: { value: responseBefore.url }, - type: { value: responseBefore.type }, - }); - if ( toLog.length !== 0 ) { - toLog.unshift(logPrefix); - safe.uboLog(toLog.join('\n')); - } - return responseAfter; - }) - 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; } - Object.defineProperty(thisArg, 'response', { value: textout }); - Object.defineProperty(thisArg, 'responseText', { value: textout }); - if ( toLog.length !== 0 ) { - toLog.unshift(logPrefix); - safe.uboLog(toLog.join('\n')); - } - }); - return Reflect.apply(target, thisArg, args); - } - }); -}; -functions['callNothrow'] = function callNothrow( - chain = '' -) { - if ( typeof chain !== 'string' ) { return; } - if ( chain === '' ) { return; } - const safe = safeSelf(); - const parts = safe.String_split.call(chain, '.'); - 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 { - } - return r; - }, - }); -}; -functions['replaceNodeTextFn'] = function replaceNodeTextFn( - nodeName = '', - pattern = '', - replacement = '', - ...varargs -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('replace-node-text.fn', ...Array.from(arguments)); - const reNodeName = safe.patternToRegex(nodeName, 'i', true); - const rePattern = safe.patternToRegex(pattern, 'gms'); - const extraArgs = safe.parseVarargs(varargs); - const reIncludes = extraArgs.includes || extraArgs.condition - ? safe.patternToRegex(extraArgs.includes || extraArgs.condition, 'ms') - : null; - const reExcludes = extraArgs.excludes - ? safe.patternToRegex(extraArgs.excludes, 'ms') - : null; - const textContentFactory = (( ) => { - const out = { createScript: s => s }; - const { trustedTypes: tt } = self; - if ( tt instanceof Object ) { - if ( typeof tt.getPropertyType === 'function' ) { - if ( tt.getPropertyType('script', 'textContent') === 'TrustedScript' ) { - return tt.createPolicy(getRandomTokenFn(), out); - } - } - } - return out; - })(); - let sedCount = extraArgs.sedCount ?? Number.MAX_SAFE_INTEGER; - const handleNode = node => { - const before = node.textContent; - if ( reIncludes ) { - reIncludes.lastIndex = 0; - if ( safe.RegExp_test(reIncludes, before) === false ) { return; } - } - if ( reExcludes ) { - reExcludes.lastIndex = 0; - if ( safe.RegExp_test(reExcludes, before) ) { return; } - } - rePattern.lastIndex = 0; - if ( safe.RegExp_test(rePattern, before) === false ) { return; } - rePattern.lastIndex = 0; - const after = pattern !== '' - ? before.replace(rePattern, replacement) - : replacement; - node.textContent = node.nodeName === 'SCRIPT' - ? textContentFactory.createScript(after) - : after; - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Text before:\n${before.trim()}`); - } - safe.uboLog(logPrefix, `Text after:\n${after.trim()}`); - sedCount -= 1; - }; - const handleTree = root => { - const treeWalker = document.createTreeWalker(root, - NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT - ); - const { currentScript } = document; - let count = 0; - for (;;) { - const node = treeWalker.nextNode(); - if ( node === null ) { break; } - count += 1; - if ( node === currentScript ) { continue; } - if ( reNodeName.test(node.nodeName) ) { - handleNode(node); - } else if ( node.nodeName === 'TEMPLATE' ) { - count += handleTree(node.content); - } else { - continue; - } - if ( sedCount === 0 ) { break; } - } - return count; - }; - if ( document.documentElement ) { - const count = handleTree(document.documentElement); - safe.uboLog(logPrefix, `${count} nodes present before installing mutation observer`); - } - const stay = Boolean(extraArgs.stay); - if ( sedCount === 0 && stay === false ) { return; } - const stop = (takeRecord = true) => { - const mutations = takeRecord ? observer.takeRecords() : []; - observer.disconnect(); - handleMutations(mutations); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, 'Quitting'); - } - }; - const handleMutations = mutations => { - for ( const mutation of mutations ) { - for ( const node of mutation.addedNodes ) { - if ( reNodeName.test(node.nodeName) ) { - handleNode(node); - } else if ( node.nodeName === 'TEMPLATE' ) { - handleTree(node.content); - } else { - continue; - } - if ( sedCount === 0 ) { return stop(false); } - } - } - }; - const observer = new MutationObserver(handleMutations); - observer.observe(document, { childList: true, subtree: true }); - if ( stay ) { return; } - runAt(( ) => { - const quitAfter = extraArgs.quitAfter ?? 0; - if ( quitAfter === 0 ) { return stop(); } - setTimeout(( ) => { stop(); }, quitAfter); - }, 'interactive'); -}; -functions['removeNodeText'] = function removeNodeText( - nodeName, - includes, - ...extraArgs -) { - replaceNodeTextFn(nodeName, '', '', 'includes', includes || '', ...extraArgs); -}; -functions['preventCanvas'] = 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); - } - }); -}; -functions['multiup'] = 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 }); -}; -functions['breakOnCall'] = function breakOnCall(target) { - proxyApplyFn(target, function(context) { - debugger; // eslint-disable-line no-debugger - return context.reflect(); - }); -}; -functions['replaceNodeText'] = function replaceNodeText( - nodeName, - pattern, - replacement, - ...extraArgs -) { - replaceNodeTextFn(nodeName, pattern, replacement, ...extraArgs); -}; -functions['replaceFetchResponseFn'] = function replaceFetchResponseFn( - trusted = false, - pattern = '', - replacement = '', - propsToMatch = '', - ...varargs -) { - if ( trusted !== true ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('replace-fetch-response', pattern, replacement, propsToMatch); - if ( pattern === '*' ) { pattern = '.*'; } - const rePattern = safe.patternToRegex(pattern); - const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url'); - const extraArgs = safe.parseVarargs(varargs); - const reIncludes = extraArgs.includes ? safe.patternToRegex(extraArgs.includes) : null; - self.fetch = new Proxy(self.fetch, { - apply: function(target, thisArg, args) { - const fetchPromise = Reflect.apply(target, thisArg, args); - if ( pattern === '' ) { return fetchPromise; } - if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(...args); - const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return fetchPromise; } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - } - return fetchPromise.then(responseBefore => { - const response = responseBefore.clone(); - return response.text().then(textBefore => { - if ( reIncludes && reIncludes.test(textBefore) === false ) { - return responseBefore; - } - const textAfter = textBefore.replace(rePattern, replacement); - if ( textAfter === textBefore ) { return responseBefore; } - safe.uboLog(logPrefix, 'Replaced'); - 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 => { - safe.uboErr(logPrefix, reason); - return responseBefore; - }); - }).catch(reason => { - safe.uboErr(logPrefix, reason); - return fetchPromise; - }); - } - }); -}; -functions['trustedReplaceFetchResponse'] = function trustedReplaceFetchResponse(...args) { - replaceFetchResponseFn(true, ...args); -}; -functions['trustedReplaceXhrResponse'] = function trustedReplaceXhrResponse( - pattern = '', - replacement = '', - propsToMatch = '', - ...varargs -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-replace-xhr-response', pattern, replacement, propsToMatch); - const xhrInstances = new WeakMap(); - if ( pattern === '*' ) { pattern = '.*'; } - const rePattern = safe.patternToRegex(pattern); - const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url'); - const extraArgs = safe.parseVarargs(varargs); - const reIncludes = extraArgs.includes ? safe.patternToRegex(extraArgs.includes) : null; - self.XMLHttpRequest = class extends self.XMLHttpRequest { - open(method, url, ...args) { - const outerXhr = this; - const xhrDetails = { method, url }; - let outcome = 'match'; - if ( propNeedles.size !== 0 ) { - if ( matchObjectPropertiesFn(propNeedles, xhrDetails) === undefined ) { - outcome = 'nomatch'; - } - } - if ( outcome === 'match' ) { - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched "propsToMatch"`); - } - xhrInstances.set(outerXhr, xhrDetails); - } - return super.open(method, url, ...args); - } - get response() { - const innerResponse = super.response; - const xhrDetails = xhrInstances.get(this); - if ( xhrDetails === undefined ) { - return innerResponse; - } - const responseLength = typeof innerResponse === 'string' - ? innerResponse.length - : undefined; - if ( xhrDetails.lastResponseLength !== responseLength ) { - xhrDetails.response = undefined; - xhrDetails.lastResponseLength = responseLength; - } - if ( xhrDetails.response !== undefined ) { - return xhrDetails.response; - } - if ( typeof innerResponse !== 'string' ) { - return (xhrDetails.response = innerResponse); - } - if ( reIncludes && reIncludes.test(innerResponse) === false ) { - return (xhrDetails.response = innerResponse); - } - const textBefore = innerResponse; - const textAfter = textBefore.replace(rePattern, replacement); - if ( textAfter !== textBefore ) { - safe.uboLog(logPrefix, 'Match'); - } - return (xhrDetails.response = textAfter); - } - get responseText() { - const response = this.response; - if ( typeof response !== 'string' ) { - return super.responseText; - } - return response; - } - }; -}; -functions['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; -}; -functions['getAllCookiesFn'] = function getAllCookiesFn() { - const safe = safeSelf(); - return safe.String_split.call(document.cookie, /\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); -}; -functions['trustedClickElement'] = function trustedClickElement( - selectors = '', - extraMatch = '', - delay = '' -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-click-element', selectors, extraMatch, delay); - - if ( extraMatch !== '' ) { - const assertions = safe.String_split.call(extraMatch, ',').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(`^${safe.escapeRegexChars(key)}=${safe.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; - } - } - } - - const steps = (( ) => { - const steps = /^[;|]/.test(selectors) - ? safe.String_split.call(selectors.slice(1), selectors.charAt(0)) - : safe.String_split.call(selectors, ','); - return steps.map(a => { - a = a.trim(); - if ( /^\d+$/.test(a) ) { return parseInt(a, 10); } - return a; - }); - })(); - if ( steps.length === 0 ) { return; } - const clickDelay = parseInt(delay, 10) || 1; - for ( let i = steps.length-1; i > 0; i-- ) { - if ( typeof steps[i] !== 'string' ) { continue; } - if ( typeof steps[i-1] !== 'string' ) { continue; } - steps.splice(i, 0, clickDelay); - } - if ( steps.length === 1 && delay !== '' ) { - steps.unshift(clickDelay); - } - if ( typeof steps.at(-1) !== 'number' ) { - steps.push(11000); - } - - const timeout = steps.pop(); - - const waitForTime = ms => { - return new Promise(resolve => { - safe.uboLog(logPrefix, `Waiting for ${ms} ms`); - waitForTime.timer = setTimeout(( ) => { - waitForTime.timer = undefined; - resolve(); - }, ms); - }); - }; - - const waitForElement = directive => { - safe.uboLog(logPrefix, `Waiting for ${directive}`); - return lookupElementsFn(directive, Date.now() + timeout).then(elems => { - if ( elems.length === 0 ) { return false; } - elems[0].click(); - safe.uboLog(logPrefix, `Clicked ${directive}`); - return true; - }); - }; - - const process = async ( ) => { - while ( steps.length !== 0 ) { - const step = steps.shift(); - if ( step === undefined ) { break; } - if ( typeof step === 'number' ) { - await waitForTime(step); - if ( step === 1 ) { continue; } - continue; - } - if ( step.startsWith('!') ) { continue; } - const clicked = await waitForElement(step); - if ( clicked ) { continue; } - safe.uboLog(logPrefix, `Timed out waiting on ${step}`); - break; - } - }; - - runAtHtmlElementFn(process); -}; -functions['trustedReplaceOutboundText'] = function trustedReplaceOutboundText( - propChain = '', - rawPattern = '', - rawReplacement = '', - ...varargs -) { - if ( propChain === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-replace-outbound-text', propChain, rawPattern, rawReplacement, ...varargs); - const rePattern = safe.patternToRegex(rawPattern); - const replacement = rawReplacement.startsWith('json:') - ? safe.JSON_parse(rawReplacement.slice(5)) - : rawReplacement; - const extraArgs = safe.parseVarargs(varargs); - const reCondition = safe.patternToRegex(extraArgs.condition || ''); - proxyApplyFn(propChain, function(context) { - const encodedTextBefore = context.reflect(); - let textBefore = encodedTextBefore; - if ( extraArgs.encoding === 'base64' ) { - try { textBefore = self.atob(encodedTextBefore); } - catch { return encodedTextBefore; } - } - if ( rawPattern === '' ) { - safe.uboLog(logPrefix, 'Decoded outbound text:\n', textBefore); - return encodedTextBefore; - } - reCondition.lastIndex = 0; - if ( reCondition.test(textBefore) === false ) { return encodedTextBefore; } - const textAfter = textBefore.replace(rePattern, replacement); - if ( textAfter === textBefore ) { return encodedTextBefore; } - safe.uboLog(logPrefix, 'Matched and replaced'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, 'Modified decoded outbound text:\n', textAfter); - } - let encodedTextAfter = textAfter; - if ( extraArgs.encoding === 'base64' ) { - encodedTextAfter = self.btoa(textAfter); - } - return encodedTextAfter; - }); -}; -functions['trustedSuppressNativeMethod'] = function trustedSuppressNativeMethod( - methodPath = '', - signature = '', - how = '', - stack = '' -) { - if ( methodPath === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-suppress-native-method', methodPath, signature, how, stack); - const signatureArgs = safe.String_split.call(signature, /\s*\|\s*/).map(v => { - if ( /^".*"$/.test(v) ) { - return { type: 'pattern', re: safe.patternToRegex(v.slice(1, -1)) }; - } - if ( /^\/.+\/$/.test(v) ) { - return { type: 'pattern', re: safe.patternToRegex(v) }; - } - if ( v === 'false' ) { - return { type: 'exact', value: false }; - } - if ( v === 'true' ) { - return { type: 'exact', value: true }; - } - if ( v === 'null' ) { - return { type: 'exact', value: null }; - } - if ( v === 'undefined' ) { - return { type: 'exact', value: undefined }; - } - }); - const stackNeedle = safe.initPattern(stack, { canNegate: true }); - proxyApplyFn(methodPath, function(context) { - const { callArgs } = context; - if ( signature === '' ) { - safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`); - return context.reflect(); - } - for ( let i = 0; i < signatureArgs.length; i++ ) { - const signatureArg = signatureArgs[i]; - if ( signatureArg === undefined ) { continue; } - const targetArg = i < callArgs.length ? callArgs[i] : undefined; - if ( signatureArg.type === 'exact' ) { - if ( targetArg !== signatureArg.value ) { - return context.reflect(); - } - } - if ( signatureArg.type === 'pattern' ) { - if ( safe.RegExp_test(signatureArg.re, targetArg) === false ) { - return context.reflect(); - } - } - } - if ( stackNeedle.matchAll !== true ) { - const logLevel = safe.logLevel > 1 ? 'all' : ''; - if ( matchesStackTraceFn(stackNeedle, logLevel) === false ) { - return context.reflect(); - } - } - if ( how === 'debug' ) { - debugger; // eslint-disable-line no-debugger - return context.reflect(); - } - safe.uboLog(logPrefix, `Suppressed:\n${callArgs.join('\n')}`); - if ( how === 'abort' ) { - throw new ReferenceError(); - } - }); -}; -functions['trustedPreventDomBypass'] = function trustedPreventDomBypass( - methodPath = '', - targetProp = '' -) { - if ( methodPath === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-prevent-dom-bypass', methodPath, targetProp); - proxyApplyFn(methodPath, function(context) { - const elems = new Set(context.callArgs.filter(e => e instanceof HTMLElement)); - const r = context.reflect(); - if ( elems.length === 0 ) { return r; } - for ( const elem of elems ) { - try { - if ( `${elem.contentWindow}` !== '[object Window]' ) { continue; } - if ( elem.contentWindow.location.href !== 'about:blank' ) { - if ( elem.contentWindow.location.href !== self.location.href ) { - continue; - } - } - if ( targetProp !== '' ) { - let me = self, it = elem.contentWindow; - let chain = targetProp; - for (;;) { - const pos = chain.indexOf('.'); - if ( pos === -1 ) { break; } - const prop = chain.slice(0, pos); - me = me[prop]; it = it[prop]; - chain = chain.slice(pos+1); - } - it[chain] = me[chain]; - } else { - Object.defineProperty(elem, 'contentWindow', { value: self }); - } - safe.uboLog(logPrefix, 'Bypass prevented'); - } catch { - } - } - return r; - }); -}; -functions['trustedOverrideElementMethod'] = function trustedOverrideElementMethod( - methodPath = '', - selector = '', - disposition = '', - ...varargs -) { - if ( methodPath === '' ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-override-element-method', methodPath, selector, disposition); - const extraArgs = safe.parseVarargs(varargs); - proxyApplyFn(methodPath, function(context) { - let override = selector === ''; - if ( override === false ) { - const { thisArg } = context; - try { - override = thisArg.closest(selector) === thisArg; - } catch { - } - } - if ( override === false ) { - return context.reflect(); - } - safe.uboLog(logPrefix, 'Overridden'); - if ( disposition === '' ) { return; } - if ( disposition === 'debug' && safe.logLevel !== 0 ) { - debugger; // eslint-disable-line no-debugger - } - if ( disposition === 'throw' ) { - throw new ReferenceError(); - } - return validateConstantFn(true, disposition, extraArgs); - }); -}; - const scriptlets = {}; scriptlets['abort-current-script.js'] = { @@ -6161,7 +27,6 @@ aliases: ["abort-current-inline-script.js","acis.js","acs.js"], requiresTrust: false, fn: 'abortCurrentScript', -dependencies: ["runAtHtmlElementFn","trapPropertyFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","abortCurrentScriptFn"], }; @@ -6170,7 +35,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: false, fn: 'setAttr', -dependencies: ["runAt","setAttrFn","safeSelf"], }; @@ -6179,7 +43,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: true, fn: 'trustedSetAttr', -dependencies: ["runAt","setAttrFn","safeSelf"], }; @@ -6188,7 +51,6 @@ aliases: ["ra.js"], requiresTrust: false, fn: 'removeAttr', -dependencies: ["safeSelf","runAt","onIdleFn","offIdleFn"], }; @@ -6197,7 +59,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: true, fn: 'trustedCreateHTML', -dependencies: ["safeSelf","runAt"], }; @@ -6206,7 +67,6 @@ aliases: ["urlskip.js"], world: 'ISOLATED', requiresTrust: false, fn: 'hrefSanitizer', -dependencies: ["urlSkip","safeSelf","runAt","onIdleFn"], }; @@ -6215,7 +75,6 @@ aliases: [], requiresTrust: false, fn: 'proxyApplyConfig', -dependencies: [], }; @@ -6224,7 +83,6 @@ aliases: [], requiresTrust: false, fn: 'editOutboundObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editOutboundObjectFn"], }; @@ -6233,7 +91,6 @@ aliases: [], requiresTrust: true, fn: 'trustedEditOutboundObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editOutboundObjectFn"], }; @@ -6242,7 +99,6 @@ aliases: [], requiresTrust: false, fn: 'jsonEdit', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","jsonEditFn"], }; @@ -6251,7 +107,6 @@ aliases: [], requiresTrust: true, fn: 'trustedJsonEdit', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","jsonEditFn"], }; @@ -6260,7 +115,6 @@ aliases: [], requiresTrust: false, fn: 'editInboundObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editInboundObjectFn"], }; @@ -6269,7 +123,6 @@ aliases: [], requiresTrust: true, fn: 'trustedEditInboundObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editInboundObjectFn"], }; @@ -6278,7 +131,6 @@ aliases: [], requiresTrust: false, fn: 'editThisObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editThisObjectFn"], }; @@ -6287,7 +139,6 @@ aliases: [], requiresTrust: true, fn: 'trustedEditThisObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editThisObjectFn"], }; @@ -6296,7 +147,6 @@ aliases: [], requiresTrust: false, fn: 'editObjectOnGetter', -dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnGetterFn"], }; @@ -6305,7 +155,6 @@ aliases: [], requiresTrust: true, fn: 'trustedEditObjectOnGetter', -dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnGetterFn"], }; @@ -6314,7 +163,6 @@ aliases: [], requiresTrust: false, fn: 'editObjectOnSetter', -dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnSetterFn"], }; @@ -6323,7 +171,6 @@ aliases: [], requiresTrust: true, fn: 'trustedEditObjectOnSetter', -dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnSetterFn"], }; @@ -6332,7 +179,6 @@ aliases: [], requiresTrust: false, fn: 'editElementObject', -dependencies: ["sleepFn","safeSelf","onIdleFn","offIdleFn","lookupElementsFn","JSONPath","editElementObjectFn"], }; @@ -6341,7 +187,6 @@ aliases: [], requiresTrust: true, fn: 'trustedEditElementObject', -dependencies: ["sleepFn","safeSelf","onIdleFn","offIdleFn","lookupElementsFn","JSONPath","editElementObjectFn"], }; @@ -6350,7 +195,6 @@ aliases: [], requiresTrust: false, fn: 'jsonEditXhrResponse', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrResponseFn"], }; @@ -6359,7 +203,6 @@ aliases: [], requiresTrust: true, fn: 'trustedJsonEditXhrResponse', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrResponseFn"], }; @@ -6368,7 +211,6 @@ aliases: [], requiresTrust: false, fn: 'jsonEditXhrRequest', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrRequestFn"], }; @@ -6377,7 +219,6 @@ aliases: [], requiresTrust: true, fn: 'trustedJsonEditXhrRequest', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrRequestFn"], }; @@ -6386,7 +227,6 @@ aliases: [], requiresTrust: false, fn: 'jsonEditFetchResponse', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchResponseFn"], }; @@ -6395,7 +235,6 @@ aliases: [], requiresTrust: true, fn: 'trustedJsonEditFetchResponse', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchResponseFn"], }; @@ -6404,7 +243,6 @@ aliases: [], requiresTrust: false, fn: 'jsonEditFetchRequest', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchRequestFn"], }; @@ -6413,7 +251,6 @@ aliases: [], requiresTrust: true, fn: 'trustedJsonEditFetchRequest', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchRequestFn"], }; @@ -6422,7 +259,6 @@ aliases: [], requiresTrust: false, fn: 'jsonlEditXhrResponse', -dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","jsonlEditFn","JSONPath","jsonlEditXhrResponseFn"], }; @@ -6431,7 +267,6 @@ aliases: [], requiresTrust: true, fn: 'trustedJsonlEditXhrResponse', -dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","jsonlEditFn","JSONPath","jsonlEditXhrResponseFn"], }; @@ -6440,7 +275,6 @@ aliases: [], requiresTrust: false, fn: 'jsonlEditFetchResponse', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","jsonlEditFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonlEditFetchResponseFn"], }; @@ -6449,7 +283,6 @@ aliases: [], requiresTrust: true, fn: 'trustedJsonlEditFetchResponse', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","jsonlEditFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonlEditFetchResponseFn"], }; @@ -6458,7 +291,6 @@ aliases: ["aost.js"], requiresTrust: false, fn: 'abortOnStackTrace', -dependencies: ["matchesStackTraceFn","safeSelf","getRandomTokenFn","getExceptionTokenFn"], }; @@ -6467,7 +299,6 @@ aliases: [], requiresTrust: true, fn: 'trustedPruneInboundObject', -dependencies: ["getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","safeSelf","objectFindOwnerFn"], }; @@ -6476,7 +307,6 @@ aliases: [], requiresTrust: true, fn: 'trustedPruneOutboundObject', -dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], }; @@ -6485,7 +315,6 @@ aliases: [], requiresTrust: false, fn: 'jsonPrune', -dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], }; @@ -6494,7 +323,6 @@ aliases: [], requiresTrust: false, fn: 'jsonPruneFetchResponse', -dependencies: ["parsePropertiesToMatchFn","objectFindOwnerFn","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","matchObjectPropertiesFn","safeSelf","collateFetchArgumentsFn"], }; @@ -6503,7 +331,6 @@ aliases: [], requiresTrust: false, fn: 'jsonPruneXhrResponse', -dependencies: ["parsePropertiesToMatchFn","objectFindOwnerFn","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","safeSelf","matchObjectPropertiesFn"], }; @@ -6512,7 +339,6 @@ aliases: [], requiresTrust: false, fn: 'evaldataPrune', -dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], }; @@ -6521,7 +347,6 @@ aliases: ["prevent-eval-if.js"], requiresTrust: false, fn: 'noEvalIf', -dependencies: ["safeSelf","proxyApplyFn"], }; @@ -6530,7 +355,6 @@ aliases: ["addEventListener-defuser.js","aeld.js"], requiresTrust: false, fn: 'preventAddEventListener', -dependencies: ["safeSelf","runAt","proxyApplyFn"], }; @@ -6539,7 +363,6 @@ aliases: ["bab-defuser.js","nobab.js"], requiresTrust: false, fn: 'preventBab', -dependencies: ["safeSelf","proxyApplyFn"], }; @@ -6548,7 +371,6 @@ aliases: [], requiresTrust: true, fn: 'preventClipboardWrite', -dependencies: ["safeSelf","runAt","proxyApplyFn"], }; @@ -6557,7 +379,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: false, fn: 'preventDialog', -dependencies: ["safeSelf"], }; @@ -6566,7 +387,6 @@ aliases: ["no-fetch-if.js"], requiresTrust: false, fn: 'preventFetch', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","generateContentFn","safeSelf","collateFetchArgumentsFn","preventFetchFn"], }; @@ -6575,7 +395,6 @@ aliases: [], requiresTrust: true, fn: 'trustedPreventFetch', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","generateContentFn","safeSelf","collateFetchArgumentsFn","preventFetchFn"], }; @@ -6584,7 +403,6 @@ aliases: [], requiresTrust: false, fn: 'freezeElementProperty', -dependencies: ["safeSelf"], }; @@ -6593,7 +411,6 @@ aliases: [], requiresTrust: false, fn: 'preventInnerHTML', -dependencies: ["safeSelf","freezeElementProperty"], }; @@ -6602,7 +419,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: false, fn: 'preventNavigation', -dependencies: ["safeSelf"], }; @@ -6611,7 +427,6 @@ aliases: ["no-setTimeout-if.js","nostif.js","setTimeout-defuser.js"], requiresTrust: false, fn: 'preventSetTimeout', -dependencies: ["safeSelf","RangeParser","proxyApplyFn"], }; @@ -6620,7 +435,6 @@ aliases: ["no-setInterval-if.js","nosiif.js","setInterval-defuser.js"], requiresTrust: false, fn: 'preventSetInterval', -dependencies: ["safeSelf","RangeParser","proxyApplyFn"], }; @@ -6629,7 +443,6 @@ aliases: ["no-requestAnimationFrame-if.js","norafif.js"], requiresTrust: false, fn: 'preventRequestAnimationFrame', -dependencies: ["safeSelf","proxyApplyFn"], }; @@ -6638,7 +451,6 @@ aliases: ["set.js"], requiresTrust: false, fn: 'setConstant', -dependencies: ["validateConstantFn","safeSelf","runAt","setConstantFn"], }; @@ -6647,7 +459,6 @@ aliases: ["trusted-set.js"], requiresTrust: true, fn: 'trustedSetConstant', -dependencies: ["validateConstantFn","safeSelf","runAt","setConstantFn"], }; @@ -6656,7 +467,6 @@ aliases: [], requiresTrust: true, fn: 'trustedReplaceArgument', -dependencies: ["validateConstantFn","safeSelf","proxyApplyFn","ArglistParser","parseReplaceFn"], }; @@ -6665,7 +475,6 @@ aliases: [], requiresTrust: false, fn: 'spoofCSS', -dependencies: ["safeSelf"], }; @@ -6674,7 +483,6 @@ aliases: ["no-xhr-if.js"], requiresTrust: false, fn: 'preventXhr', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","generateContentFn","preventXhrFn"], }; @@ -6683,7 +491,6 @@ aliases: [], requiresTrust: false, fn: 'trustedPreventXhr', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","generateContentFn","preventXhrFn"], }; @@ -6692,7 +499,6 @@ aliases: [], requiresTrust: false, fn: 'mpegdashPrune', -dependencies: ["proxyToStringFn","parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","modifyXhrResponseFn"], }; @@ -6701,7 +507,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: false, fn: 'setCookie', -dependencies: ["getCookieFn","setCookieFn","safeSelf","getSafeCookieValuesFn"], }; @@ -6710,7 +515,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: false, fn: 'setCookieReload', -dependencies: ["getCookieFn","setCookieFn","safeSelf","getSafeCookieValuesFn","setCookie"], }; @@ -6719,7 +523,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: true, fn: 'trustedSetCookie', -dependencies: ["getCookieFn","setCookieFn","safeSelf"], }; @@ -6728,7 +531,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: true, fn: 'trustedSetCookieReload', -dependencies: ["getCookieFn","setCookieFn","safeSelf","trustedSetCookie"], }; @@ -6737,7 +539,6 @@ aliases: ["cookie-remover.js"], world: 'ISOLATED', requiresTrust: false, fn: 'removeCookie', -dependencies: ["safeSelf"], }; @@ -6746,7 +547,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: false, fn: 'setLocalStorageItem', -dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], }; @@ -6755,7 +555,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: false, fn: 'setSessionStorageItem', -dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], }; @@ -6764,7 +563,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: true, fn: 'trustedSetLocalStorageItem', -dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], }; @@ -6773,7 +571,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: true, fn: 'trustedSetSessionStorageItem', -dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], }; @@ -6782,7 +579,6 @@ aliases: ["aopr.js"], requiresTrust: false, fn: 'abortOnPropertyRead', -dependencies: ["safeSelf","getRandomTokenFn","getExceptionTokenFn"], }; @@ -6791,7 +587,6 @@ aliases: ["aopw.js"], requiresTrust: false, fn: 'abortOnPropertyWrite', -dependencies: ["safeSelf","getRandomTokenFn","getExceptionTokenFn"], }; @@ -6800,7 +595,6 @@ aliases: ["nano-setInterval-booster.js","nano-sib.js"], requiresTrust: false, fn: 'adjustSetInterval', -dependencies: ["safeSelf"], }; @@ -6809,7 +603,6 @@ aliases: ["nano-setTimeout-booster.js","nano-stb.js"], requiresTrust: false, fn: 'adjustSetTimeout', -dependencies: ["safeSelf"], }; @@ -6818,7 +611,6 @@ aliases: ["refresh-defuser.js"], world: 'ISOLATED', requiresTrust: false, fn: 'preventRefresh', -dependencies: ["safeSelf"], }; @@ -6827,7 +619,6 @@ aliases: ["rc.js"], world: 'ISOLATED', requiresTrust: false, fn: 'removeClass', -dependencies: ["safeSelf","runAt","onIdleFn"], }; @@ -6836,7 +627,6 @@ aliases: [], requiresTrust: false, fn: 'webrtcIf', -dependencies: ["safeSelf"], }; @@ -6845,7 +635,6 @@ aliases: ["nowoif.js","no-window-open-if.js","window.open-defuser.js"], requiresTrust: false, fn: 'noWindowOpenIf', -dependencies: ["safeSelf","proxyApplyFn"], }; @@ -6854,7 +643,6 @@ aliases: ["window-close-if.js"], world: 'ISOLATED', requiresTrust: false, fn: 'closeWindow', -dependencies: ["safeSelf"], }; @@ -6863,7 +651,6 @@ aliases: [], requiresTrust: false, fn: 'windowNameDefuser', -dependencies: [], }; @@ -6872,7 +659,6 @@ aliases: [], requiresTrust: false, fn: 'overlayBuster', -dependencies: [], }; @@ -6881,7 +667,6 @@ aliases: [], requiresTrust: false, fn: 'alertBuster', -dependencies: [], }; @@ -6890,7 +675,6 @@ aliases: [], requiresTrust: false, fn: 'noWebrtc', -dependencies: [], }; @@ -6899,7 +683,6 @@ aliases: [], requiresTrust: false, fn: 'disableNewtabLinks', -dependencies: [], }; @@ -6908,7 +691,6 @@ aliases: [], requiresTrust: false, fn: 'xmlPrune', -dependencies: ["proxyToStringFn","parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","modifyXhrResponseFn"], }; @@ -6917,7 +699,6 @@ aliases: [], requiresTrust: false, fn: 'm3uPrune', -dependencies: ["safeSelf","proxyApplyFn"], }; @@ -6926,7 +707,6 @@ aliases: [], requiresTrust: false, fn: 'callNothrow', -dependencies: ["safeSelf"], }; @@ -6935,7 +715,6 @@ aliases: ["rmnt.js"], world: 'ISOLATED', requiresTrust: false, fn: 'removeNodeText', -dependencies: ["runAt","safeSelf","getRandomTokenFn","replaceNodeTextFn"], }; @@ -6944,7 +723,6 @@ aliases: [], requiresTrust: false, fn: 'preventCanvas', -dependencies: ["safeSelf"], }; @@ -6953,7 +731,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: false, fn: 'multiup', -dependencies: [], }; @@ -6962,7 +739,6 @@ aliases: [], requiresTrust: false, fn: 'breakOnCall', -dependencies: ["proxyApplyFn"], }; @@ -6971,7 +747,6 @@ aliases: ["trusted-rpnt.js","replace-node-text.js","rpnt.js"], world: 'ISOLATED', requiresTrust: true, fn: 'replaceNodeText', -dependencies: ["runAt","safeSelf","getRandomTokenFn","replaceNodeTextFn"], }; @@ -6980,7 +755,6 @@ aliases: ["trusted-rpfr.js"], requiresTrust: true, fn: 'trustedReplaceFetchResponse', -dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","collateFetchArgumentsFn","replaceFetchResponseFn"], }; @@ -6989,7 +763,6 @@ aliases: [], requiresTrust: true, fn: 'trustedReplaceXhrResponse', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn"], }; @@ -6998,7 +771,6 @@ aliases: [], world: 'ISOLATED', requiresTrust: true, fn: 'trustedClickElement', -dependencies: ["runAtHtmlElementFn","onIdleFn","offIdleFn","lookupElementsFn","getAllLocalStorageFn","safeSelf","getAllCookiesFn"], }; @@ -7007,7 +779,6 @@ aliases: [], requiresTrust: true, fn: 'trustedReplaceOutboundText', -dependencies: ["safeSelf","proxyApplyFn"], }; @@ -7016,7 +787,6 @@ aliases: [], requiresTrust: true, fn: 'trustedSuppressNativeMethod', -dependencies: ["proxyApplyFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn"], }; @@ -7025,7 +795,6 @@ aliases: [], requiresTrust: true, fn: 'trustedPreventDomBypass', -dependencies: ["safeSelf","proxyApplyFn"], }; @@ -7034,22 +803,200 @@ aliases: [], requiresTrust: true, fn: 'trustedOverrideElementMethod', -dependencies: ["validateConstantFn","safeSelf","proxyApplyFn"], }; -// For chrome.scripting.executeScript(), which takes a function and JSON arguments but no code. +// The calls come last, so the class dependencies, which are not hoisted, are declared by then. export function run(scriptletGlobals, calls) { -function runAtHtmlElementFn(fn) { - if ( document.documentElement ) { - fn(); - return; +function safeSelf() { + if ( safeSelf.safe ) { + return safeSelf.safe; + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object': Object, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), + 'Object_fromEntries': Object.fromEntries.bind(Object), + 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), + 'Object_hasOwn': Object.hasOwn.bind(Object), + 'Object_toString': Object.prototype.toString, + 'RegExp': self.RegExp, + 'RegExp_test': Function.prototype.call.bind(self.RegExp.prototype.test), + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'String': self.String, + 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parse': Function.prototype.call.bind(self.JSON.parse, self.JSON), + 'JSON_stringify': Function.prototype.call.bind(self.JSON.stringify, self.JSON), + 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, + uboLog(...args) { + 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); + }, + escapeRegexChars(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true, expect: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + 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, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = this.escapeRegexChars(pattern); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch { + } + return /^/; + }, + parseVarargs(varargs) { + const entries = varargs.reduce((out, v, i, a) => { + if ( i & 1 ) { return out; } + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + return out; + }, []); + return this.Object_fromEntries(entries); + }, + }; + safeSelf.safe = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + 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?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; } - const observer = new MutationObserver(( ) => { - observer.disconnect(); - fn(); - }); - observer.observe(document, { childList: true }); + return safe; +} +function getRandomTokenFn() { + const safe = safeSelf(); + return safe.String_fromCharCode(Date.now() % 26 + 97) + + safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); +} +function getExceptionTokenFn() { + const token = getRandomTokenFn(); + 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; } function trapPropertyFn(propChain, handler, options = {}) { if ( propChain === '' ) { return; } @@ -7119,199 +1066,301 @@ function trapPropertyFn(propChain, handler, options = {}) { trapPropertyFn.setter(owner, prop, value); } }); - } catch { + } catch { + } + return entry.value; +} +function collateFetchArgumentsFn(resource, options) { + const safe = safeSelf(); + const props = [ + 'body', 'cache', 'credentials', 'duplex', 'headers', + 'integrity', 'keepalive', 'method', 'mode', 'priority', + 'redirect', 'referrer', 'referrerPolicy', 'url' + ]; + const out = {}; + if ( collateFetchArgumentsFn.collateKnownProps === undefined ) { + collateFetchArgumentsFn.collateKnownProps = (src, out) => { + for ( const prop of props ) { + if ( src[prop] === undefined ) { continue; } + out[prop] = src[prop]; + } + }; + } + if ( + typeof resource !== 'object' || + safe.Object_toString.call(resource) !== '[object Request]' + ) { + out.url = `${resource}`; + } else { + let clone; + try { + clone = safe.Request_clone.call(resource); + } catch { + } + collateFetchArgumentsFn.collateKnownProps(clone || resource, out); + } + if ( typeof options === 'object' && options !== null ) { + collateFetchArgumentsFn.collateKnownProps(options, out); + } + return out; +} +function parsePropertiesToMatchFn(propsToMatch, implicit = '') { + const safe = safeSelf(); + const needles = new Map(); + if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } + const options = { canNegate: true }; + for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { + let [ prop, pattern ] = safe.String_split.call(needle, ':'); + if ( prop === '' ) { continue; } + if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { + prop = `${prop}:${pattern}`; + pattern = undefined; + } + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); + } + } + return needles; +} +function matchObjectPropertiesFn(propNeedles, ...objs) { + const safe = safeSelf(); + const matched = []; + for ( const obj of objs ) { + if ( obj instanceof Object === false ) { continue; } + for ( const [ prop, details ] of propNeedles ) { + let value = obj[prop]; + if ( value === undefined ) { continue; } + if ( typeof value !== 'string' ) { + try { value = safe.JSON_stringify(value); } + catch { } + if ( typeof value !== 'string' ) { continue; } + } + if ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); + } + } + return matched; +} +function generateContentFn(trusted, 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 randomize(10); + } + if ( directive === 'emptyObj' ) { + return '{}'; + } + if ( directive === 'emptyArr' ) { + return '[]'; + } + if ( directive === 'emptyStr' ) { + return ''; + } + if ( directive.startsWith('length:') ) { + const match = /^length:(\d+)(?:-(\d+))?$/.exec(directive); + if ( match === null ) { return ''; } + 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 randomize(len | 0); + } + if ( directive.startsWith('war:') ) { + if ( scriptletGlobals.warOrigin === undefined ) { return ''; } + return new Promise(resolve => { + const warOrigin = scriptletGlobals.warOrigin; + const warName = directive.slice(4); + const fullpath = [ warOrigin, '/', warName ]; + const warSecret = scriptletGlobals.warSecret; + 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(); + }).catch(( ) => ''); + } + if ( directive.startsWith('join:') ) { + const parts = directive.slice(7) + .split(directive.slice(5, 7)) + .map(a => generateContentFn(trusted, a)); + return parts.some(a => a instanceof Promise) + ? Promise.all(parts).then(parts => parts.join('')) + : parts.join(''); + } + if ( trusted ) { + return directive; + } + return ''; +} +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); +} +function offIdleFn(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); +} +function sleepFn(ms = 0) { + const nap = ( ) => { + return new Promise(resolve => { + self.requestAnimationFrame(resolve); + }); } - return entry.value; + const until = Date.now() + ms; + const sleep = async resolve => { + do { + await nap(); + } while ( Date.now() < until ); + resolve(); + }; + return new Promise(resolve => { sleep(resolve); }); } -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; - } - const self = globalThis; - const safe = { - 'Array_from': Array.from, - 'Error': self.Error, - 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), - 'Math_floor': Math.floor, - 'Math_max': Math.max, - 'Math_min': Math.min, - 'Math_random': Math.random, - 'Object': Object, - 'Object_defineProperty': Object.defineProperty.bind(Object), - 'Object_defineProperties': Object.defineProperties.bind(Object), - 'Object_fromEntries': Object.fromEntries.bind(Object), - 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), - 'Object_hasOwn': Object.hasOwn.bind(Object), - 'Object_toString': Object.prototype.toString, - 'RegExp': self.RegExp, - 'RegExp_test': Function.prototype.call.bind(self.RegExp.prototype.test), - 'RegExp_exec': self.RegExp.prototype.exec, - 'Request_clone': self.Request.prototype.clone, - 'String': self.String, - 'String_fromCharCode': String.fromCharCode, - 'String_split': String.prototype.split, - 'XMLHttpRequest': self.XMLHttpRequest, - 'addEventListener': self.EventTarget.prototype.addEventListener, - 'removeEventListener': self.EventTarget.prototype.removeEventListener, - 'fetch': self.fetch, - 'JSON': self.JSON, - 'JSON_parse': Function.prototype.call.bind(self.JSON.parse, self.JSON), - 'JSON_stringify': Function.prototype.call.bind(self.JSON.stringify, self.JSON), - 'log': console.log.bind(console), - // Properties - logLevel: 0, - // Methods - makeLogPrefix(...args) { - return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; - }, - uboLog(...args) { - 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); - }, - escapeRegexChars(s) { - return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - }, - initPattern(pattern, options = {}) { - if ( pattern === '' ) { - return { matchAll: true, expect: true }; - } - const expect = (options.canNegate !== true || pattern.startsWith('!') === false); - if ( expect === false ) { - pattern = pattern.slice(1); +function lookupElementsFn(directive, until = 0) { + if ( lookupElementsFn.querySelectorEx === undefined ) { + lookupElementsFn.getShadowRoot = elem => { + if ( elem.openOrClosedShadowRoot ) { // Firefox + return elem.openOrClosedShadowRoot; } - const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); - if ( match !== null ) { - return { - re: new this.RegExp( - match[1], - match[2] || options.flags - ), - expect, - }; + if ( self.chrome?.dom?.openOrClosedShadowRoot ) { // Chromium + return self.chrome.dom.openOrClosedShadowRoot(elem); } - if ( options.flags !== undefined ) { - return { - re: new this.RegExp(this.escapeRegexChars(pattern), - options.flags - ), - expect, - }; + return elem.shadowRoot; + }; + lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { + if ( selector.startsWith('xpath:') === false ) { + return Array.from(context.querySelectorAll(selector)); } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + const result = document.evaluate(selector.slice(6), context, null, 7, null); + const out = []; + if ( result.resultType === 7 ) { + for ( let i = 0; i < result.snapshotLength; i++ ) { + out[i] = result.snapshotItem(i); + } } - return haystack.includes(details.pattern) === details.expect; - }, - patternToRegex(pattern, flags = undefined, verbatim = false) { - if ( pattern === '' ) { return /^/; } - const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); - if ( match === null ) { - const reStr = this.escapeRegexChars(pattern); - return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + return out; + } + lookupElementsFn.querySelectorEx = (selector, context = document) => { + const pos = selector.indexOf(' >>> '); + if ( pos === -1 ) { + return lookupElementsFn.queryOrEvaluateSelector(selector, context); } - try { - return new RegExp(match[1], match[2] || undefined); + const outside = selector.slice(0, pos).trim(); + const inside = selector.slice(pos + 5).trim(); + const elems = lookupElementsFn.queryOrEvaluateSelector(outside, context); + const out = []; + for ( let i = 0; i < elems.length; i++ ) { + const shadowRoot = lookupElementsFn.getShadowRoot(elems[i]); + if ( Boolean(shadowRoot) === false ) { continue; } + lookupElementsFn.querySelectorEx(inside, shadowRoot).forEach(a => out.push(a)); } - catch { + return out; + }; + lookupElementsFn.lookup = directive => { + const beVisible = directive.startsWith('when-visible:'); + const selector = beVisible ? directive.slice(13) : directive; + const elems = lookupElementsFn.querySelectorEx(selector); + if ( beVisible !== true ) { return elems; } + return elems.filter(a => a.checkVisibility({ + opacityProperty: true, + visibilityProperty: true, + })); + }; + lookupElementsFn.lookupAsync = details => { + const elems = lookupElementsFn.lookup(details.directive); + if ( elems.length || Date.now() >= details.until ) { + if ( details.observer ) { + details.observer.disconnect(); + details.observer = undefined; + } + if ( details.timer ) { + offIdleFn(details.timer); + details.timer = undefined; + } + return details.resolve(elems); } - return /^/; - }, - parseVarargs(varargs) { - const entries = varargs.reduce((out, v, i, a) => { - if ( i & 1 ) { return out; } - const rawValue = a[i+1]; - const value = /^\d+$/.test(rawValue) - ? parseInt(rawValue, 10) - : rawValue; - out.push([ a[i], value ]); - return out; - }, []); - return this.Object_fromEntries(entries); - }, - }; - safeSelf.safe = safe; - if ( scriptletGlobals.bcSecret === undefined ) { return safe; } - // This is executed only when the logger is opened - safe.logLevel = scriptletGlobals.logLevel || 1; - let lastLogType = ''; - let lastLogText = ''; - let lastLogTime = 0; - safe.toLogText = (type, ...args) => { - if ( args.length === 0 ) { return; } - const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; - if ( text === lastLogText && type === lastLogType ) { - if ( (Date.now() - lastLogTime) < 5000 ) { return; } - } - lastLogType = type; - lastLogText = text; - lastLogTime = Date.now(); - return text; - }; - try { - const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); - let bcBuffer = []; - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - if ( bcBuffer === undefined ) { - return bc.postMessage({ what: 'messageToLogger', type, text }); + if ( details.observer === undefined ) { + details.observer = new MutationObserver(( ) => { + lookupElementsFn.lookupAsync(details); + }); + details.observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }); } - 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; + if ( details.timer === undefined ) { + details.timer = onIdleFn(( ) => { + details.timer = undefined; + lookupElementsFn.lookupAsync(details); + }, { timeout: 151 }); } }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); + } + if ( until === 0 ) { + return lookupElementsFn.lookup(directive); + } + return new Promise(resolve => { + lookupElementsFn.lookupAsync({ directive, until, resolve }); + }); +} +function runAt(fn, when) { + const intFromReadyState = state => { + const targets = { + 'loading': 1, 'asap': 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 ( Object.hasOwn(targets, prop) === false ) { continue; } + return targets[prop]; + } + return 0; + }; + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; } - return safe; -} -function getRandomTokenFn() { + const onStateChange = ( ) => { + if ( intFromReadyState(document.readyState) < runAt ) { return; } + fn(); + safe.removeEventListener.apply(document, args); + }; const safe = safeSelf(); - return safe.String_fromCharCode(Date.now() % 26 + 97) + - safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); + const args = [ 'readystatechange', onStateChange, { capture: true } ]; + safe.addEventListener.apply(document, args); } -function getExceptionTokenFn() { - const token = getRandomTokenFn(); - 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; +function runAtHtmlElementFn(fn) { + if ( document.documentElement ) { + fn(); + return; + } + const observer = new MutationObserver(( ) => { + observer.disconnect(); + fn(); + }); + observer.observe(document, { childList: true }); } function abortCurrentScriptFn( target = '', @@ -7380,34 +1429,6 @@ function abortCurrentScript(...args) { abortCurrentScriptFn(...args); }); } -function runAt(fn, when) { - const intFromReadyState = state => { - const targets = { - 'loading': 1, 'asap': 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 ( Object.hasOwn(targets, prop) === false ) { continue; } - return targets[prop]; - } - return 0; - }; - const runAt = intFromReadyState(when); - if ( intFromReadyState(document.readyState) >= runAt ) { - fn(); return; - } - const onStateChange = ( ) => { - if ( intFromReadyState(document.readyState) < runAt ) { return; } - fn(); - safe.removeEventListener.apply(document, args); - }; - const safe = safeSelf(); - const args = [ 'readystatechange', onStateChange, { capture: true } ]; - safe.addEventListener.apply(document, args); -} function setAttrFn( trusted = false, logPrefix, @@ -7509,18 +1530,6 @@ function trustedSetAttr( const options = safe.parseVarargs(varargs); setAttrFn(true, logPrefix, selector, attr, value, options); } -function onIdleFn(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); -} -function offIdleFn(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); -} function removeAttr( rawToken = '', rawSelector = '', @@ -7885,112 +1894,98 @@ function hrefSanitizer( }; runAt(( ) => { start(); }, 'interactive'); } -function proxyApplyConfig(config = '') { - try { - if ( typeof proxyApplyFn !== 'function' ) { return; } - config = JSON.parse(config); - if ( typeof config !== 'object' ) { return; } - Object.assign(proxyApplyFn, config); - } catch { - } -} -function proxyApplyFn( - target = '', - handler = '', - options = {} -) { - 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); +class ArglistParser { + constructor(separatorChar = ',', mustQuote = false) { + this.separatorChar = this.actualSeparatorChar = separatorChar; + this.separatorCode = this.actualSeparatorCode = separatorChar.charCodeAt(0); + this.mustQuote = mustQuote; + this.quoteBeg = 0; this.quoteEnd = 0; + this.argBeg = 0; this.argEnd = 0; + this.separatorBeg = 0; this.separatorEnd = 0; + this.transform = false; + this.failed = false; + this.reWhitespaceStart = /^\s+/; + this.reWhitespaceEnd = /(?:^|\S)(\s+)$/; + this.reOddTrailingEscape = /(?:^|[^\\])(?:\\\\)*\\$/; + this.reTrailingEscapeChars = /\\+$/; } - const fn = context[prop]; - if ( typeof fn !== 'function' ) { return; } - if ( proxyApplyFn.CtorContext === undefined ) { - proxyApplyFn.ctorContexts = []; - proxyApplyFn.CtorContext = class { - constructor(...args) { - this.init(...args); - } - init(callFn, callArgs) { - this.callFn = callFn; - this.callArgs = callArgs; - return this; - } - reflect() { - const r = Reflect.construct(this.callFn, this.callArgs); - this.callFn = this.callArgs = this.private = undefined; - proxyApplyFn.ctorContexts.push(this); - return r; - } - static factory(...args) { - return proxyApplyFn.ctorContexts.length !== 0 - ? proxyApplyFn.ctorContexts.pop().init(...args) - : new proxyApplyFn.CtorContext(...args); - } - }; - proxyApplyFn.applyContexts = []; - proxyApplyFn.ApplyContext = class { - constructor(...args) { - this.init(...args); - } - init(callFn, thisArg, callArgs) { - this.callFn = callFn; - this.thisArg = thisArg; - this.callArgs = callArgs; - return this; - } - reflect() { - const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs); - this.callFn = this.thisArg = this.callArgs = this.private = undefined; - proxyApplyFn.applyContexts.push(this); - return r; - } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); - } - }; - proxyApplyFn.isCtor = new Map(); - proxyApplyFn.proxies = new WeakMap(); - if ( (options.skipToString || proxyApplyFn.skipToString) !== true ) { - proxyApplyFn.nativeToString = Function.prototype.toString; - const proxiedToString = new Proxy(Function.prototype.toString, { - apply(target, thisArg) { - let proxied = thisArg; - for(;;) { - const fn = proxyApplyFn.proxies.get(proxied); - if ( fn === undefined ) { break; } - proxied = fn; - } - return proxyApplyFn.nativeToString.call(proxied); + nextArg(pattern, beg = 0) { + const len = pattern.length; + this.quoteBeg = beg + this.leftWhitespaceCount(pattern.slice(beg)); + this.failed = false; + const qc = pattern.charCodeAt(this.quoteBeg); + if ( qc === 0x22 /* " */ || qc === 0x27 /* ' */ || qc === 0x60 /* ` */ ) { + this.indexOfNextArgSeparator(pattern, qc); + if ( this.argEnd !== len ) { + this.quoteEnd = this.argEnd + 1; + this.separatorBeg = this.separatorEnd = this.quoteEnd; + this.separatorEnd += this.leftWhitespaceCount(pattern.slice(this.quoteEnd)); + if ( this.separatorEnd === len ) { return this; } + if ( pattern.charCodeAt(this.separatorEnd) === this.separatorCode ) { + this.separatorEnd += 1; + return this; } - }); - proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); - Function.prototype.toString = proxiedToString; + } + } + this.indexOfNextArgSeparator(pattern, this.separatorCode); + this.separatorBeg = this.separatorEnd = this.argEnd; + if ( this.separatorBeg < len ) { + this.separatorEnd += 1; + } + this.argEnd -= this.rightWhitespaceCount(pattern.slice(0, this.separatorBeg)); + this.quoteEnd = this.argEnd; + if ( this.mustQuote ) { + this.failed = true; } + return this; } - if ( proxyApplyFn.isCtor.has(target) === false ) { - proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); + normalizeArg(s, char = '') { + if ( char === '' ) { char = this.actualSeparatorChar; } + let out = ''; + let pos = 0; + while ( (pos = s.lastIndexOf(char)) !== -1 ) { + out = s.slice(pos) + out; + s = s.slice(0, pos); + const match = this.reTrailingEscapeChars.exec(s); + if ( match === null ) { continue; } + const tail = (match[0].length & 1) !== 0 + ? match[0].slice(0, -1) + : match[0]; + out = tail + out; + s = s.slice(0, -match[0].length); + } + if ( out === '' ) { return s; } + return s + out; } - const proxyDetails = { - apply(target, thisArg, args) { - return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); + leftWhitespaceCount(s) { + const match = this.reWhitespaceStart.exec(s); + return match === null ? 0 : match[0].length; + } + rightWhitespaceCount(s) { + const match = this.reWhitespaceEnd.exec(s); + return match === null ? 0 : match[1].length; + } + indexOfNextArgSeparator(pattern, separatorCode) { + this.argBeg = this.argEnd = separatorCode !== this.separatorCode + ? this.quoteBeg + 1 + : this.quoteBeg; + this.transform = false; + if ( separatorCode !== this.actualSeparatorCode ) { + this.actualSeparatorCode = separatorCode; + this.actualSeparatorChar = String.fromCharCode(separatorCode); + } + while ( this.argEnd < pattern.length ) { + const pos = pattern.indexOf(this.actualSeparatorChar, this.argEnd); + if ( pos === -1 ) { + return (this.argEnd = pattern.length); + } + if ( this.reOddTrailingEscape.test(pattern.slice(0, pos)) === false ) { + return (this.argEnd = pos); + } + this.transform = true; + this.argEnd = pos + 1; } - }; - if ( proxyApplyFn.isCtor.get(target) ) { - proxyDetails.construct = function(target, args) { - return handler(proxyApplyFn.CtorContext.factory(target, args)); - }; } - const proxiedTarget = new Proxy(fn, proxyDetails); - proxyApplyFn.proxies.set(proxiedTarget, fn); - context[prop] = proxiedTarget; } class JSONPath { static create(query) { @@ -8540,6 +2535,133 @@ class JSONPath { } } } +function proxyToStringFn(proxiedFn, nativeFn) { + if ( proxyToStringFn.proxies === undefined ) { + proxyToStringFn.proxies = new WeakMap(); + proxyToStringFn.nativeToString = Function.prototype.toString; + const proxiedToString = new Proxy(Function.prototype.toString, { + apply(target, thisArg) { + let proxied = thisArg; + for(;;) { + const fn = proxyToStringFn.proxies.get(proxied); + if ( fn === undefined ) { break; } + proxied = fn; + } + return proxyToStringFn.nativeToString.call(proxied); + } + }); + proxyToStringFn.proxies.set(proxiedToString, proxyToStringFn.nativeToString); + Function.prototype.toString = proxiedToString; + } + proxyToStringFn.proxies.set(proxiedFn, nativeFn); +} +function proxyApplyFn( + target = '', + handler = '', + options = {} +) { + 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 ( proxyApplyFn.CtorContext === undefined ) { + proxyApplyFn.ctorContexts = []; + proxyApplyFn.CtorContext = class { + constructor(...args) { + this.init(...args); + } + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); + } + }; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); + } + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs); + this.callFn = this.thisArg = this.callArgs = this.private = undefined; + proxyApplyFn.applyContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); + } + }; + proxyApplyFn.isCtor = new Map(); + proxyApplyFn.proxies = new WeakMap(); + if ( (options.skipToString || proxyApplyFn.skipToString) !== true ) { + proxyApplyFn.nativeToString = Function.prototype.toString; + const proxiedToString = new Proxy(Function.prototype.toString, { + apply(target, thisArg) { + let proxied = thisArg; + for(;;) { + const fn = proxyApplyFn.proxies.get(proxied); + if ( fn === undefined ) { break; } + proxied = fn; + } + return proxyApplyFn.nativeToString.call(proxied); + } + }); + proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); + Function.prototype.toString = proxiedToString; + } + } + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); + } + const proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); + } + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; + } + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +function proxyApplyConfig(config = '') { + try { + if ( typeof proxyApplyFn !== 'function' ) { return; } + config = JSON.parse(config); + if ( typeof config !== 'object' ) { return; } + Object.assign(proxyApplyFn, config); + } catch { + } +} function editOutboundObjectFn( trusted = false, propChain = '', @@ -8776,125 +2898,22 @@ function editObjectOnSetterFn( get: function() { return currentValue; }, - set: function(a) { - currentValue = a; - if ( currentValue instanceof Object ) { - editObj(currentValue); - } - } - }); - if ( currentValue instanceof Object ) { - editObj(currentValue); - } -} -function editObjectOnSetter(...args) { - editObjectOnSetterFn(false, ...args); -} -function trustedEditObjectOnSetter(...args) { - editObjectOnSetterFn(true, ...args); -} -function sleepFn(ms = 0) { - const nap = ( ) => { - return new Promise(resolve => { - self.requestAnimationFrame(resolve); - }); - } - const until = Date.now() + ms; - const sleep = async resolve => { - do { - await nap(); - } while ( Date.now() < until ); - resolve(); - }; - return new Promise(resolve => { sleep(resolve); }); -} -function lookupElementsFn(directive, until = 0) { - if ( lookupElementsFn.querySelectorEx === undefined ) { - lookupElementsFn.getShadowRoot = elem => { - if ( elem.openOrClosedShadowRoot ) { // Firefox - return elem.openOrClosedShadowRoot; - } - if ( self.chrome?.dom?.openOrClosedShadowRoot ) { // Chromium - return self.chrome.dom.openOrClosedShadowRoot(elem); - } - return elem.shadowRoot; - }; - lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { - if ( selector.startsWith('xpath:') === false ) { - return Array.from(context.querySelectorAll(selector)); - } - const result = document.evaluate(selector.slice(6), context, null, 7, null); - const out = []; - if ( result.resultType === 7 ) { - for ( let i = 0; i < result.snapshotLength; i++ ) { - out[i] = result.snapshotItem(i); - } - } - return out; - } - lookupElementsFn.querySelectorEx = (selector, context = document) => { - const pos = selector.indexOf(' >>> '); - if ( pos === -1 ) { - return lookupElementsFn.queryOrEvaluateSelector(selector, context); - } - const outside = selector.slice(0, pos).trim(); - const inside = selector.slice(pos + 5).trim(); - const elems = lookupElementsFn.queryOrEvaluateSelector(outside, context); - const out = []; - for ( let i = 0; i < elems.length; i++ ) { - const shadowRoot = lookupElementsFn.getShadowRoot(elems[i]); - if ( Boolean(shadowRoot) === false ) { continue; } - lookupElementsFn.querySelectorEx(inside, shadowRoot).forEach(a => out.push(a)); - } - return out; - }; - lookupElementsFn.lookup = directive => { - const beVisible = directive.startsWith('when-visible:'); - const selector = beVisible ? directive.slice(13) : directive; - const elems = lookupElementsFn.querySelectorEx(selector); - if ( beVisible !== true ) { return elems; } - return elems.filter(a => a.checkVisibility({ - opacityProperty: true, - visibilityProperty: true, - })); - }; - lookupElementsFn.lookupAsync = details => { - const elems = lookupElementsFn.lookup(details.directive); - if ( elems.length || Date.now() >= details.until ) { - if ( details.observer ) { - details.observer.disconnect(); - details.observer = undefined; - } - if ( details.timer ) { - offIdleFn(details.timer); - details.timer = undefined; - } - return details.resolve(elems); - } - if ( details.observer === undefined ) { - details.observer = new MutationObserver(( ) => { - lookupElementsFn.lookupAsync(details); - }); - details.observer.observe(document, { - attributes: true, - childList: true, - subtree: true, - }); - } - if ( details.timer === undefined ) { - details.timer = onIdleFn(( ) => { - details.timer = undefined; - lookupElementsFn.lookupAsync(details); - }, { timeout: 151 }); - } - }; - } - if ( until === 0 ) { - return lookupElementsFn.lookup(directive); - } - return new Promise(resolve => { - lookupElementsFn.lookupAsync({ directive, until, resolve }); + set: function(a) { + currentValue = a; + if ( currentValue instanceof Object ) { + editObj(currentValue); + } + } }); + if ( currentValue instanceof Object ) { + editObj(currentValue); + } +} +function editObjectOnSetter(...args) { + editObjectOnSetterFn(false, ...args); +} +function trustedEditObjectOnSetter(...args) { + editObjectOnSetterFn(true, ...args); } async function editElementObjectFn( trusted = false, @@ -8937,45 +2956,6 @@ function editElementObject(...args) { function trustedEditElementObject(...args) { editElementObjectFn(true, ...args); } -function parsePropertiesToMatchFn(propsToMatch, implicit = '') { - const safe = safeSelf(); - const needles = new Map(); - if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } - const options = { canNegate: true }; - for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { - let [ prop, pattern ] = safe.String_split.call(needle, ':'); - if ( prop === '' ) { continue; } - if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { - prop = `${prop}:${pattern}`; - pattern = undefined; - } - if ( pattern !== undefined ) { - needles.set(prop, safe.initPattern(pattern, options)); - } else if ( implicit !== '' ) { - needles.set(implicit, safe.initPattern(prop, options)); - } - } - return needles; -} -function matchObjectPropertiesFn(propNeedles, ...objs) { - const safe = safeSelf(); - const matched = []; - for ( const obj of objs ) { - if ( obj instanceof Object === false ) { continue; } - for ( const [ prop, details ] of propNeedles ) { - let value = obj[prop]; - if ( value === undefined ) { continue; } - if ( typeof value !== 'string' ) { - try { value = safe.JSON_stringify(value); } - catch { } - if ( typeof value !== 'string' ) { continue; } - } - if ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); - } - } - return matched; -} function jsonEditXhrResponseFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( @@ -9105,40 +3085,6 @@ function jsonEditXhrRequest(jsonq = '', ...args) { function trustedJsonEditXhrRequest(jsonq = '', ...args) { jsonEditXhrRequestFn(true, jsonq, ...args); } -function collateFetchArgumentsFn(resource, options) { - const safe = safeSelf(); - const props = [ - 'body', 'cache', 'credentials', 'duplex', 'headers', - 'integrity', 'keepalive', 'method', 'mode', 'priority', - 'redirect', 'referrer', 'referrerPolicy', 'url' - ]; - const out = {}; - if ( collateFetchArgumentsFn.collateKnownProps === undefined ) { - collateFetchArgumentsFn.collateKnownProps = (src, out) => { - for ( const prop of props ) { - if ( src[prop] === undefined ) { continue; } - out[prop] = src[prop]; - } - }; - } - if ( - typeof resource !== 'object' || - safe.Object_toString.call(resource) !== '[object Request]' - ) { - out.url = `${resource}`; - } else { - let clone; - try { - clone = safe.Request_clone.call(resource); - } catch { - } - collateFetchArgumentsFn.collateKnownProps(clone || resource, out); - } - if ( typeof options === 'object' && options !== null ) { - collateFetchArgumentsFn.collateKnownProps(options, out); - } - return out; -} function jsonEditFetchResponseFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( @@ -9517,47 +3463,6 @@ function abortOnStackTrace( const owner = window; makeProxy(owner, chain); } -function objectPruneFn( - obj, - rawPrunePaths, - rawNeedlePaths, - stackNeedleDetails = { matchAll: true }, - extraArgs = {} -) { - if ( typeof rawPrunePaths !== 'string' ) { return; } - const safe = safeSelf(); - const prunePaths = rawPrunePaths !== '' - ? safe.String_split.call(rawPrunePaths, / +/) - : []; - const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== '' - ? safe.String_split.call(rawNeedlePaths, / +/) - : []; - if ( stackNeedleDetails.matchAll !== true ) { - if ( matchesStackTraceFn(stackNeedleDetails, extraArgs.logstack) === false ) { - return; - } - } - if ( objectPruneFn.mustProcess === undefined ) { - objectPruneFn.mustProcess = (root, needlePaths) => { - for ( const needlePath of needlePaths ) { - if ( objectFindOwnerFn(root, needlePath) === false ) { - return false; - } - } - return true; - }; - } - if ( prunePaths.length === 0 ) { return; } - let outcome = 'nomatch'; - if ( objectPruneFn.mustProcess(obj, needlePaths) ) { - for ( const path of prunePaths ) { - if ( objectFindOwnerFn(obj, path, true) ) { - outcome = 'match'; - } - } - } - if ( outcome === 'match' ) { return obj; } -} function objectFindOwnerFn( root, path, @@ -9622,6 +3527,47 @@ function objectFindOwnerFn( chain = chain.slice(pos + 1); } } +function objectPruneFn( + obj, + rawPrunePaths, + rawNeedlePaths, + stackNeedleDetails = { matchAll: true }, + extraArgs = {} +) { + if ( typeof rawPrunePaths !== 'string' ) { return; } + const safe = safeSelf(); + const prunePaths = rawPrunePaths !== '' + ? safe.String_split.call(rawPrunePaths, / +/) + : []; + const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== '' + ? safe.String_split.call(rawNeedlePaths, / +/) + : []; + if ( stackNeedleDetails.matchAll !== true ) { + if ( matchesStackTraceFn(stackNeedleDetails, extraArgs.logstack) === false ) { + return; + } + } + if ( objectPruneFn.mustProcess === undefined ) { + objectPruneFn.mustProcess = (root, needlePaths) => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; + } + } + return true; + }; + } + if ( prunePaths.length === 0 ) { return; } + let outcome = 'nomatch'; + if ( objectPruneFn.mustProcess(obj, needlePaths) ) { + for ( const path of prunePaths ) { + if ( objectFindOwnerFn(obj, path, true) ) { + outcome = 'match'; + } + } + } + if ( outcome === 'match' ) { return obj; } +} function trustedPruneInboundObject( entryPoint = '', argPos = '', @@ -10195,71 +4141,6 @@ function preventDialog( subtree: true, }); } -function generateContentFn(trusted, 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 randomize(10); - } - if ( directive === 'emptyObj' ) { - return '{}'; - } - if ( directive === 'emptyArr' ) { - return '[]'; - } - if ( directive === 'emptyStr' ) { - return ''; - } - if ( directive.startsWith('length:') ) { - const match = /^length:(\d+)(?:-(\d+))?$/.exec(directive); - if ( match === null ) { return ''; } - 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 randomize(len | 0); - } - if ( directive.startsWith('war:') ) { - if ( scriptletGlobals.warOrigin === undefined ) { return ''; } - return new Promise(resolve => { - const warOrigin = scriptletGlobals.warOrigin; - const warName = directive.slice(4); - const fullpath = [ warOrigin, '/', warName ]; - const warSecret = scriptletGlobals.warSecret; - 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(); - }).catch(( ) => ''); - } - if ( directive.startsWith('join:') ) { - const parts = directive.slice(7) - .split(directive.slice(5, 7)) - .map(a => generateContentFn(trusted, a)); - return parts.some(a => a instanceof Promise) - ? Promise.all(parts).then(parts => parts.join('')) - : parts.join(''); - } - if ( trusted ) { - return directive; - } - return ''; -} function preventFetchFn( trusted = false, propsToMatch = '', @@ -10528,6 +4409,27 @@ function preventRequestAnimationFrame( return context.reflect(); }); } +function parseReplaceFn(s) { + if ( s.charCodeAt(0) !== 0x2F /* / */ ) { return; } + const parser = new ArglistParser('/'); + parser.nextArg(s, 1); + let pattern = s.slice(parser.argBeg, parser.argEnd); + if ( parser.transform ) { + pattern = parser.normalizeArg(pattern); + } + if ( pattern === '' ) { return; } + parser.nextArg(s, parser.separatorEnd); + let replacement = s.slice(parser.argBeg, parser.argEnd); + if ( parser.separatorEnd === parser.separatorBeg ) { return; } + if ( parser.transform ) { + replacement = parser.normalizeArg(replacement); + } + const flags = s.slice(parser.separatorEnd); + try { + return { re: new RegExp(pattern, flags), replacement }; + } catch { + } +} function validateConstantFn(trusted, raw, extraArgs = {}) { const safe = safeSelf(); let value; @@ -10739,120 +4641,6 @@ function trustedSetConstant( ) { setConstantFn(true, ...args); } -class ArglistParser { - constructor(separatorChar = ',', mustQuote = false) { - this.separatorChar = this.actualSeparatorChar = separatorChar; - this.separatorCode = this.actualSeparatorCode = separatorChar.charCodeAt(0); - this.mustQuote = mustQuote; - this.quoteBeg = 0; this.quoteEnd = 0; - this.argBeg = 0; this.argEnd = 0; - this.separatorBeg = 0; this.separatorEnd = 0; - this.transform = false; - this.failed = false; - this.reWhitespaceStart = /^\s+/; - this.reWhitespaceEnd = /(?:^|\S)(\s+)$/; - this.reOddTrailingEscape = /(?:^|[^\\])(?:\\\\)*\\$/; - this.reTrailingEscapeChars = /\\+$/; - } - nextArg(pattern, beg = 0) { - const len = pattern.length; - this.quoteBeg = beg + this.leftWhitespaceCount(pattern.slice(beg)); - this.failed = false; - const qc = pattern.charCodeAt(this.quoteBeg); - if ( qc === 0x22 /* " */ || qc === 0x27 /* ' */ || qc === 0x60 /* ` */ ) { - this.indexOfNextArgSeparator(pattern, qc); - if ( this.argEnd !== len ) { - this.quoteEnd = this.argEnd + 1; - this.separatorBeg = this.separatorEnd = this.quoteEnd; - this.separatorEnd += this.leftWhitespaceCount(pattern.slice(this.quoteEnd)); - if ( this.separatorEnd === len ) { return this; } - if ( pattern.charCodeAt(this.separatorEnd) === this.separatorCode ) { - this.separatorEnd += 1; - return this; - } - } - } - this.indexOfNextArgSeparator(pattern, this.separatorCode); - this.separatorBeg = this.separatorEnd = this.argEnd; - if ( this.separatorBeg < len ) { - this.separatorEnd += 1; - } - this.argEnd -= this.rightWhitespaceCount(pattern.slice(0, this.separatorBeg)); - this.quoteEnd = this.argEnd; - if ( this.mustQuote ) { - this.failed = true; - } - return this; - } - normalizeArg(s, char = '') { - if ( char === '' ) { char = this.actualSeparatorChar; } - let out = ''; - let pos = 0; - while ( (pos = s.lastIndexOf(char)) !== -1 ) { - out = s.slice(pos) + out; - s = s.slice(0, pos); - const match = this.reTrailingEscapeChars.exec(s); - if ( match === null ) { continue; } - const tail = (match[0].length & 1) !== 0 - ? match[0].slice(0, -1) - : match[0]; - out = tail + out; - s = s.slice(0, -match[0].length); - } - if ( out === '' ) { return s; } - return s + out; - } - leftWhitespaceCount(s) { - const match = this.reWhitespaceStart.exec(s); - return match === null ? 0 : match[0].length; - } - rightWhitespaceCount(s) { - const match = this.reWhitespaceEnd.exec(s); - return match === null ? 0 : match[1].length; - } - indexOfNextArgSeparator(pattern, separatorCode) { - this.argBeg = this.argEnd = separatorCode !== this.separatorCode - ? this.quoteBeg + 1 - : this.quoteBeg; - this.transform = false; - if ( separatorCode !== this.actualSeparatorCode ) { - this.actualSeparatorCode = separatorCode; - this.actualSeparatorChar = String.fromCharCode(separatorCode); - } - while ( this.argEnd < pattern.length ) { - const pos = pattern.indexOf(this.actualSeparatorChar, this.argEnd); - if ( pos === -1 ) { - return (this.argEnd = pattern.length); - } - if ( this.reOddTrailingEscape.test(pattern.slice(0, pos)) === false ) { - return (this.argEnd = pos); - } - this.transform = true; - this.argEnd = pos + 1; - } - } -} -function parseReplaceFn(s) { - if ( s.charCodeAt(0) !== 0x2F /* / */ ) { return; } - const parser = new ArglistParser('/'); - parser.nextArg(s, 1); - let pattern = s.slice(parser.argBeg, parser.argEnd); - if ( parser.transform ) { - pattern = parser.normalizeArg(pattern); - } - if ( pattern === '' ) { return; } - parser.nextArg(s, parser.separatorEnd); - let replacement = s.slice(parser.argBeg, parser.argEnd); - if ( parser.separatorEnd === parser.separatorBeg ) { return; } - if ( parser.transform ) { - replacement = parser.normalizeArg(replacement); - } - const flags = s.slice(parser.separatorEnd); - try { - return { re: new RegExp(pattern, flags), replacement }; - } catch { - } -} function trustedReplaceArgument( propChain = '', argposRaw = '', @@ -11034,6 +4822,50 @@ function spoofCSS( }, }); } +function modifyXhrResponseFn( + propsToMatch = '', + modifierFn = '' +) { + if ( typeof propsToMatch !== 'string' ) { return; } + const safe = safeSelf(); + if ( modifyXhrResponseFn.xhrInstances === undefined ) { + modifyXhrResponseFn.xhrInstances = new WeakMap(); + } + const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url'); + const NativeXMLHttpRequest = self.XMLHttpRequest; + const TrappedXMLHttpRequest = class XMLHttpRequest extends NativeXMLHttpRequest { + open(method, url, ...args) { + const haystack = { method, url }; + if ( propsToMatch === '' ) { + safe.uboLog(`modifyXhrResponseFn() / Called: ${safe.JSON_stringify(haystack, null, 2)}`); + } else if ( matchObjectPropertiesFn(propNeedles, haystack) ) { + modifyXhrResponseFn.xhrInstances.set(this, modifierFn); + } + return super.open(method, url, ...args); + } + get response() { + const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); + return modifierFn + ? modifierFn(this, super.response) + : super.response; + } + get responseText() { + const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); + return modifierFn + ? modifierFn(this, super.responseText) + : super.responseText; + } + get responseXML() { + const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); + return modifierFn + ? modifierFn(this, super.responseXML) + : super.responseXML; + } + }; + proxyToStringFn(TrappedXMLHttpRequest.prototype.open, NativeXMLHttpRequest.prototype.open); + proxyToStringFn(TrappedXMLHttpRequest, NativeXMLHttpRequest); + self.XMLHttpRequest = TrappedXMLHttpRequest; +} function preventXhrFn( trusted = false, propsToMatch = '', @@ -11155,121 +4987,57 @@ function preventXhrFn( readyState: { value: 2, configurable: true }, status: { value: 200 }, statusText: { value: 'OK' }, - }); - safeDispatchEvent(details.xhr, 'readystatechange'); - return details; - }).then(details => { - Object.defineProperties(details.xhr, { - readyState: { value: 3, configurable: true }, - }); - Object.defineProperties(details.xhr, details.props); - safeDispatchEvent(details.xhr, 'readystatechange'); - return details; - }).then(details => { - Object.defineProperties(details.xhr, { - readyState: { value: 4 }, - }); - safeDispatchEvent(details.xhr, 'readystatechange'); - safeDispatchEvent(details.xhr, 'load'); - safeDispatchEvent(details.xhr, 'loadend'); - safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`); - }); - }); - proxyApplyFn('XMLHttpRequest.prototype.getResponseHeader', function(context) { - const { thisArg } = context; - const xhrDetails = xhrInstances.get(thisArg); - if ( xhrDetails === undefined || thisArg.readyState < thisArg.HEADERS_RECEIVED ) { - return context.reflect(); - } - const headerName = `${context.callArgs[0]}`; - const value = xhrDetails.headers[headerName.toLowerCase()]; - if ( value !== undefined && value !== '' ) { return value; } - return null; - }); - proxyApplyFn('XMLHttpRequest.prototype.getAllResponseHeaders', function(context) { - const { thisArg } = context; - const xhrDetails = xhrInstances.get(thisArg); - if ( xhrDetails === undefined || thisArg.readyState < thisArg.HEADERS_RECEIVED ) { - return context.reflect(); - } - const out = []; - for ( const [ name, value ] of Object.entries(xhrDetails.headers) ) { - if ( !value ) { continue; } - out.push(`${name}: ${value}`); - } - if ( out.length !== 0 ) { out.push(''); } - return out.join('\r\n'); - }); -} -function preventXhr(...args) { - preventXhrFn(false, ...args); -} -function trustedPreventXhr(...args) { - preventXhrFn(true, ...args); -} -function proxyToStringFn(proxiedFn, nativeFn) { - if ( proxyToStringFn.proxies === undefined ) { - proxyToStringFn.proxies = new WeakMap(); - proxyToStringFn.nativeToString = Function.prototype.toString; - const proxiedToString = new Proxy(Function.prototype.toString, { - apply(target, thisArg) { - let proxied = thisArg; - for(;;) { - const fn = proxyToStringFn.proxies.get(proxied); - if ( fn === undefined ) { break; } - proxied = fn; - } - return proxyToStringFn.nativeToString.call(proxied); - } - }); - proxyToStringFn.proxies.set(proxiedToString, proxyToStringFn.nativeToString); - Function.prototype.toString = proxiedToString; - } - proxyToStringFn.proxies.set(proxiedFn, nativeFn); -} -function modifyXhrResponseFn( - propsToMatch = '', - modifierFn = '' -) { - if ( typeof propsToMatch !== 'string' ) { return; } - const safe = safeSelf(); - if ( modifyXhrResponseFn.xhrInstances === undefined ) { - modifyXhrResponseFn.xhrInstances = new WeakMap(); - } - const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url'); - const NativeXMLHttpRequest = self.XMLHttpRequest; - const TrappedXMLHttpRequest = class XMLHttpRequest extends NativeXMLHttpRequest { - open(method, url, ...args) { - const haystack = { method, url }; - if ( propsToMatch === '' ) { - safe.uboLog(`modifyXhrResponseFn() / Called: ${safe.JSON_stringify(haystack, null, 2)}`); - } else if ( matchObjectPropertiesFn(propNeedles, haystack) ) { - modifyXhrResponseFn.xhrInstances.set(this, modifierFn); - } - return super.open(method, url, ...args); - } - get response() { - const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); - return modifierFn - ? modifierFn(this, super.response) - : super.response; + }); + safeDispatchEvent(details.xhr, 'readystatechange'); + return details; + }).then(details => { + Object.defineProperties(details.xhr, { + readyState: { value: 3, configurable: true }, + }); + Object.defineProperties(details.xhr, details.props); + safeDispatchEvent(details.xhr, 'readystatechange'); + return details; + }).then(details => { + Object.defineProperties(details.xhr, { + readyState: { value: 4 }, + }); + safeDispatchEvent(details.xhr, 'readystatechange'); + safeDispatchEvent(details.xhr, 'load'); + safeDispatchEvent(details.xhr, 'loadend'); + safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`); + }); + }); + proxyApplyFn('XMLHttpRequest.prototype.getResponseHeader', function(context) { + const { thisArg } = context; + const xhrDetails = xhrInstances.get(thisArg); + if ( xhrDetails === undefined || thisArg.readyState < thisArg.HEADERS_RECEIVED ) { + return context.reflect(); } - get responseText() { - const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); - return modifierFn - ? modifierFn(this, super.responseText) - : super.responseText; + const headerName = `${context.callArgs[0]}`; + const value = xhrDetails.headers[headerName.toLowerCase()]; + if ( value !== undefined && value !== '' ) { return value; } + return null; + }); + proxyApplyFn('XMLHttpRequest.prototype.getAllResponseHeaders', function(context) { + const { thisArg } = context; + const xhrDetails = xhrInstances.get(thisArg); + if ( xhrDetails === undefined || thisArg.readyState < thisArg.HEADERS_RECEIVED ) { + return context.reflect(); } - get responseXML() { - const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); - return modifierFn - ? modifierFn(this, super.responseXML) - : super.responseXML; + const out = []; + for ( const [ name, value ] of Object.entries(xhrDetails.headers) ) { + if ( !value ) { continue; } + out.push(`${name}: ${value}`); } - }; - proxyToStringFn(TrappedXMLHttpRequest.prototype.open, NativeXMLHttpRequest.prototype.open); - proxyToStringFn(TrappedXMLHttpRequest, NativeXMLHttpRequest); - self.XMLHttpRequest = TrappedXMLHttpRequest; + if ( out.length !== 0 ) { out.push(''); } + return out.join('\r\n'); + }); +} +function preventXhr(...args) { + preventXhrFn(false, ...args); +} +function trustedPreventXhr(...args) { + preventXhrFn(true, ...args); } function mpegdashPrune( selector = '', @@ -11413,6 +5181,45 @@ function mpegdashPrune( return before; }); } +function getSafeCookieValuesFn() { + return [ + 'accept', 'reject', + 'accepted', 'rejected', 'notaccepted', + 'allow', 'disallow', 'deny', + 'allowed', 'denied', + 'approved', 'disapproved', + 'checked', 'unchecked', + 'dismiss', 'dismissed', + 'enable', 'disable', + 'enabled', 'disabled', + 'essential', 'nonessential', + 'forbidden', 'forever', + 'hide', 'hidden', + 'necessary', 'required', + 'ok', + 'on', 'off', + 'true', 't', 'false', 'f', + 'yes', 'y', 'no', 'n', + 'all', 'none', 'functional', + 'granted', 'done', + 'decline', 'declined', + 'closed', 'next', 'mandatory', + 'disagree', 'agree', + 'set', 'unset', + 'given', + ]; +} +function getAllCookiesFn() { + const safe = safeSelf(); + return safe.String_split.call(document.cookie, /\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); +} function getCookieFn( name = '' ) { @@ -11490,34 +5297,6 @@ function setCookieFn( return done; } -function getSafeCookieValuesFn() { - return [ - 'accept', 'reject', - 'accepted', 'rejected', 'notaccepted', - 'allow', 'disallow', 'deny', - 'allowed', 'denied', - 'approved', 'disapproved', - 'checked', 'unchecked', - 'dismiss', 'dismissed', - 'enable', 'disable', - 'enabled', 'disabled', - 'essential', 'nonessential', - 'forbidden', 'forever', - 'hide', 'hidden', - 'necessary', 'required', - 'ok', - 'on', 'off', - 'true', 't', 'false', 'f', - 'yes', 'y', 'no', 'n', - 'all', 'none', 'functional', - 'granted', 'done', - 'decline', 'declined', - 'closed', 'next', 'mandatory', - 'disagree', 'agree', - 'set', 'unset', - 'given', - ]; -} function setCookie( name = '', value = '', @@ -11677,6 +5456,16 @@ function removeCookie( }, { passive: true }); } } +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; +} function setLocalStorageItemFn( which = 'local', trusted = false, @@ -11769,10 +5558,183 @@ function trustedSetLocalStorageItem(key = '', value = '', ...varargs) { const options = safe.parseVarargs(varargs) setLocalStorageItemFn('local', true, key, value, options); } -function trustedSetSessionStorageItem(key = '', value = '', ...varargs) { +function trustedSetSessionStorageItem(key = '', value = '', ...varargs) { + const safe = safeSelf(); + const options = safe.parseVarargs(varargs) + setLocalStorageItemFn('session', true, key, value, options); +} +function replaceNodeTextFn( + nodeName = '', + pattern = '', + replacement = '', + ...varargs +) { + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('replace-node-text.fn', ...Array.from(arguments)); + const reNodeName = safe.patternToRegex(nodeName, 'i', true); + const rePattern = safe.patternToRegex(pattern, 'gms'); + const extraArgs = safe.parseVarargs(varargs); + const reIncludes = extraArgs.includes || extraArgs.condition + ? safe.patternToRegex(extraArgs.includes || extraArgs.condition, 'ms') + : null; + const reExcludes = extraArgs.excludes + ? safe.patternToRegex(extraArgs.excludes, 'ms') + : null; + const textContentFactory = (( ) => { + const out = { createScript: s => s }; + const { trustedTypes: tt } = self; + if ( tt instanceof Object ) { + if ( typeof tt.getPropertyType === 'function' ) { + if ( tt.getPropertyType('script', 'textContent') === 'TrustedScript' ) { + return tt.createPolicy(getRandomTokenFn(), out); + } + } + } + return out; + })(); + let sedCount = extraArgs.sedCount ?? Number.MAX_SAFE_INTEGER; + const handleNode = node => { + const before = node.textContent; + if ( reIncludes ) { + reIncludes.lastIndex = 0; + if ( safe.RegExp_test(reIncludes, before) === false ) { return; } + } + if ( reExcludes ) { + reExcludes.lastIndex = 0; + if ( safe.RegExp_test(reExcludes, before) ) { return; } + } + rePattern.lastIndex = 0; + if ( safe.RegExp_test(rePattern, before) === false ) { return; } + rePattern.lastIndex = 0; + const after = pattern !== '' + ? before.replace(rePattern, replacement) + : replacement; + node.textContent = node.nodeName === 'SCRIPT' + ? textContentFactory.createScript(after) + : after; + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Text before:\n${before.trim()}`); + } + safe.uboLog(logPrefix, `Text after:\n${after.trim()}`); + sedCount -= 1; + }; + const handleTree = root => { + const treeWalker = document.createTreeWalker(root, + NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT + ); + const { currentScript } = document; + let count = 0; + for (;;) { + const node = treeWalker.nextNode(); + if ( node === null ) { break; } + count += 1; + if ( node === currentScript ) { continue; } + if ( reNodeName.test(node.nodeName) ) { + handleNode(node); + } else if ( node.nodeName === 'TEMPLATE' ) { + count += handleTree(node.content); + } else { + continue; + } + if ( sedCount === 0 ) { break; } + } + return count; + }; + if ( document.documentElement ) { + const count = handleTree(document.documentElement); + safe.uboLog(logPrefix, `${count} nodes present before installing mutation observer`); + } + const stay = Boolean(extraArgs.stay); + if ( sedCount === 0 && stay === false ) { return; } + const stop = (takeRecord = true) => { + const mutations = takeRecord ? observer.takeRecords() : []; + observer.disconnect(); + handleMutations(mutations); + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, 'Quitting'); + } + }; + const handleMutations = mutations => { + for ( const mutation of mutations ) { + for ( const node of mutation.addedNodes ) { + if ( reNodeName.test(node.nodeName) ) { + handleNode(node); + } else if ( node.nodeName === 'TEMPLATE' ) { + handleTree(node.content); + } else { + continue; + } + if ( sedCount === 0 ) { return stop(false); } + } + } + }; + const observer = new MutationObserver(handleMutations); + observer.observe(document, { childList: true, subtree: true }); + if ( stay ) { return; } + runAt(( ) => { + const quitAfter = extraArgs.quitAfter ?? 0; + if ( quitAfter === 0 ) { return stop(); } + setTimeout(( ) => { stop(); }, quitAfter); + }, 'interactive'); +} +function replaceFetchResponseFn( + trusted = false, + pattern = '', + replacement = '', + propsToMatch = '', + ...varargs +) { + if ( trusted !== true ) { return; } const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('session', true, key, value, options); + const logPrefix = safe.makeLogPrefix('replace-fetch-response', pattern, replacement, propsToMatch); + if ( pattern === '*' ) { pattern = '.*'; } + const rePattern = safe.patternToRegex(pattern); + const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url'); + const extraArgs = safe.parseVarargs(varargs); + const reIncludes = extraArgs.includes ? safe.patternToRegex(extraArgs.includes) : null; + self.fetch = new Proxy(self.fetch, { + apply: function(target, thisArg, args) { + const fetchPromise = Reflect.apply(target, thisArg, args); + if ( pattern === '' ) { return fetchPromise; } + if ( propNeedles.size !== 0 ) { + const props = collateFetchArgumentsFn(...args); + const matched = matchObjectPropertiesFn(propNeedles, props); + if ( matched === undefined ) { return fetchPromise; } + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); + } + } + return fetchPromise.then(responseBefore => { + const response = responseBefore.clone(); + return response.text().then(textBefore => { + if ( reIncludes && reIncludes.test(textBefore) === false ) { + return responseBefore; + } + const textAfter = textBefore.replace(rePattern, replacement); + if ( textAfter === textBefore ) { return responseBefore; } + safe.uboLog(logPrefix, 'Replaced'); + 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 => { + safe.uboErr(logPrefix, reason); + return responseBefore; + }); + }).catch(reason => { + safe.uboErr(logPrefix, reason); + return fetchPromise; + }); + } + }); } function abortOnPropertyRead( chain = '' @@ -12558,120 +6520,6 @@ function callNothrow( }, }); } -function replaceNodeTextFn( - nodeName = '', - pattern = '', - replacement = '', - ...varargs -) { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('replace-node-text.fn', ...Array.from(arguments)); - const reNodeName = safe.patternToRegex(nodeName, 'i', true); - const rePattern = safe.patternToRegex(pattern, 'gms'); - const extraArgs = safe.parseVarargs(varargs); - const reIncludes = extraArgs.includes || extraArgs.condition - ? safe.patternToRegex(extraArgs.includes || extraArgs.condition, 'ms') - : null; - const reExcludes = extraArgs.excludes - ? safe.patternToRegex(extraArgs.excludes, 'ms') - : null; - const textContentFactory = (( ) => { - const out = { createScript: s => s }; - const { trustedTypes: tt } = self; - if ( tt instanceof Object ) { - if ( typeof tt.getPropertyType === 'function' ) { - if ( tt.getPropertyType('script', 'textContent') === 'TrustedScript' ) { - return tt.createPolicy(getRandomTokenFn(), out); - } - } - } - return out; - })(); - let sedCount = extraArgs.sedCount ?? Number.MAX_SAFE_INTEGER; - const handleNode = node => { - const before = node.textContent; - if ( reIncludes ) { - reIncludes.lastIndex = 0; - if ( safe.RegExp_test(reIncludes, before) === false ) { return; } - } - if ( reExcludes ) { - reExcludes.lastIndex = 0; - if ( safe.RegExp_test(reExcludes, before) ) { return; } - } - rePattern.lastIndex = 0; - if ( safe.RegExp_test(rePattern, before) === false ) { return; } - rePattern.lastIndex = 0; - const after = pattern !== '' - ? before.replace(rePattern, replacement) - : replacement; - node.textContent = node.nodeName === 'SCRIPT' - ? textContentFactory.createScript(after) - : after; - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Text before:\n${before.trim()}`); - } - safe.uboLog(logPrefix, `Text after:\n${after.trim()}`); - sedCount -= 1; - }; - const handleTree = root => { - const treeWalker = document.createTreeWalker(root, - NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT - ); - const { currentScript } = document; - let count = 0; - for (;;) { - const node = treeWalker.nextNode(); - if ( node === null ) { break; } - count += 1; - if ( node === currentScript ) { continue; } - if ( reNodeName.test(node.nodeName) ) { - handleNode(node); - } else if ( node.nodeName === 'TEMPLATE' ) { - count += handleTree(node.content); - } else { - continue; - } - if ( sedCount === 0 ) { break; } - } - return count; - }; - if ( document.documentElement ) { - const count = handleTree(document.documentElement); - safe.uboLog(logPrefix, `${count} nodes present before installing mutation observer`); - } - const stay = Boolean(extraArgs.stay); - if ( sedCount === 0 && stay === false ) { return; } - const stop = (takeRecord = true) => { - const mutations = takeRecord ? observer.takeRecords() : []; - observer.disconnect(); - handleMutations(mutations); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, 'Quitting'); - } - }; - const handleMutations = mutations => { - for ( const mutation of mutations ) { - for ( const node of mutation.addedNodes ) { - if ( reNodeName.test(node.nodeName) ) { - handleNode(node); - } else if ( node.nodeName === 'TEMPLATE' ) { - handleTree(node.content); - } else { - continue; - } - if ( sedCount === 0 ) { return stop(false); } - } - } - }; - const observer = new MutationObserver(handleMutations); - observer.observe(document, { childList: true, subtree: true }); - if ( stay ) { return; } - runAt(( ) => { - const quitAfter = extraArgs.quitAfter ?? 0; - if ( quitAfter === 0 ) { return stop(); } - setTimeout(( ) => { stop(); }, quitAfter); - }, 'interactive'); -} function removeNodeText( nodeName, includes, @@ -12721,65 +6569,6 @@ function replaceNodeText( ) { replaceNodeTextFn(nodeName, pattern, replacement, ...extraArgs); } -function replaceFetchResponseFn( - trusted = false, - pattern = '', - replacement = '', - propsToMatch = '', - ...varargs -) { - if ( trusted !== true ) { return; } - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('replace-fetch-response', pattern, replacement, propsToMatch); - if ( pattern === '*' ) { pattern = '.*'; } - const rePattern = safe.patternToRegex(pattern); - const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url'); - const extraArgs = safe.parseVarargs(varargs); - const reIncludes = extraArgs.includes ? safe.patternToRegex(extraArgs.includes) : null; - self.fetch = new Proxy(self.fetch, { - apply: function(target, thisArg, args) { - const fetchPromise = Reflect.apply(target, thisArg, args); - if ( pattern === '' ) { return fetchPromise; } - if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(...args); - const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return fetchPromise; } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - } - return fetchPromise.then(responseBefore => { - const response = responseBefore.clone(); - return response.text().then(textBefore => { - if ( reIncludes && reIncludes.test(textBefore) === false ) { - return responseBefore; - } - const textAfter = textBefore.replace(rePattern, replacement); - if ( textAfter === textBefore ) { return responseBefore; } - safe.uboLog(logPrefix, 'Replaced'); - 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 => { - safe.uboErr(logPrefix, reason); - return responseBefore; - }); - }).catch(reason => { - safe.uboErr(logPrefix, reason); - return fetchPromise; - }); - } - }); -} function trustedReplaceFetchResponse(...args) { replaceFetchResponseFn(true, ...args); } @@ -12853,27 +6642,6 @@ function trustedReplaceXhrResponse( } }; } -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; -} -function getAllCookiesFn() { - const safe = safeSelf(); - return safe.String_split.call(document.cookie, /\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); -} function trustedClickElement( selectors = '', extraMatch = '', @@ -13178,6 +6946,5 @@ for (const [name, ...args] of calls) { } } -export { functions }; export default scriptlets;