From c2753af7857c32e4b7aa7555998e0b44c4e3ad96 Mon Sep 17 00:00:00 2001 From: Krzysztof Modras Date: Mon, 5 Oct 2026 16:16:53 +0200 Subject: [PATCH 1/3] Support injecting scriptlets in one shared scope --- build.ts | 75 +- index.js | 24 +- test.js | 55 +- ubo.js | 58468 ++++++++++++++++++++++++++++++++--------------------- 4 files changed, 35585 insertions(+), 23037 deletions(-) diff --git a/build.ts b/build.ts index 24fbbf5..6cce4d7 100644 --- a/build.ts +++ b/build.ts @@ -31,31 +31,11 @@ const shareSafeSelf = `try { } `; -console.log(` -/******************************************************************************* - - uBlock Origin - a comprehensive, efficient content blocker - Copyright (C) 2019-present Raymond Hill - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - - Home: https://github.com/gorhill/uBlock +// Each function once, by name, so compose() and run() can declare them in one shared scope. +const functions = new Map(); +const entryFunctions = []; -*/ -const scriptlets = {}; - -${scriptlets +const entries = scriptlets .filter(scriptlet => scriptlet.name.endsWith('.js')) .map((scriptlet) => { const allDependencies = new Set(); @@ -72,11 +52,18 @@ ${scriptlets 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))}, func: function (scriptletGlobals = {}, ...args) { ${deps.map((dep) => dep.toString()).join('\n')} ${scriptlet.fn.toString()}; @@ -85,7 +72,45 @@ ${deps.includes(safeSelf) ? shareSafeSelf : ''}${scriptlet.fn.name}(...args); }; `; }) - .join('\n')} + .join('\n'); + +console.log(` +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock + +*/ +const functions = {}; +${[...functions].map(([name, source]) => `functions['${name}'] = ${source};`).join('\n')} + +const scriptlets = {}; +${entries} + +// For chrome.scripting.executeScript(), which takes a function and JSON arguments but no code. +export function run(scriptletGlobals, calls) { +${[...functions.values()].join('\n')} +const table = { ${entryFunctions.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 447709c..0798790 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import SCRIPTLETS from './ubo.js'; +import SCRIPTLETS, { functions, run } from './ubo.js'; const scriptlets = {}; @@ -9,4 +9,26 @@ 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 41b6ecf..d8a6217 100644 --- a/test.js +++ b/test.js @@ -1,7 +1,7 @@ import { test, suite } from "node:test"; import assert from "node:assert"; import vm from "node:vm"; -import scriptlets from "./index.js"; +import scriptlets, { compose, run } from "./index.js"; test("default export is an object", () => { assert(typeof scriptlets === "object"); @@ -30,11 +30,13 @@ suite("uBO", () => { suite("safeSelf() cache", () => { // Each hook clones the argument with safe.JSON_parse(safe.JSON_stringify(obj)) - const func = scriptlets["trusted-edit-inbound-object.js"].func; + const scriptlet = scriptlets["trusted-edit-inbound-object.js"]; + const func = scriptlet.func; const HOOKS = 6; + const hookArgs = (i) => ["JSON.stringify", "0", `[?.hook${i}]+={"edited${i}":true}`]; // Same code as the extension makes for each scriptlet; the first argument is scriptletGlobals const hooks = Array.from({ length: HOOKS }, (_, i) => - `(${func})(...${JSON.stringify([{}, "JSON.stringify", "0", `[?.hook${i}]+={"edited${i}":true}`])});` + `(${func})(...${JSON.stringify([{}, ...hookArgs(i)])});` ); // A fresh realm is the page; counters wrap the native JSON methods before the scriptlets run @@ -52,12 +54,7 @@ suite("safeSelf() cache", () => { return page; } - test("is shared in a realm, so stacked hooks stay linear", () => { - const page = createPage(); - for (const hook of hooks) { - vm.runInContext(hook, page); - } - + function assertLinear(page) { const result = vm.runInContext( "calls.parse = 0; calls.stringify = 0; JSON.stringify({ hook0: true, hook5: true });", page @@ -65,17 +62,51 @@ 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 }); + } + + function safeSelfSymbols(page) { + return vm + .runInContext("Object.getOwnPropertySymbols(globalThis)", page) + .filter((key) => key.description.startsWith("safeSelf.")); + } + + test("is shared in a realm, so stacked hooks stay linear", () => { + const page = createPage(); + for (const hook of hooks) { + vm.runInContext(hook, page); + } + assertLinear(page); }); test("is read-only and not enumerable", () => { const page = createPage(); vm.runInContext(hooks.join("\n"), page); - const keys = vm - .runInContext("Object.getOwnPropertySymbols(globalThis)", page) - .filter((key) => key.description.startsWith("safeSelf.")); + const keys = safeSelfSymbols(page); assert.strictEqual(keys.length, 1); const { value, ...flags } = Object.getOwnPropertyDescriptor(page, keys[0]); assert.deepStrictEqual(flags, { writable: false, enumerable: false, configurable: false }); }); + + 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); + assert.strictEqual(safeSelfSymbols(page).length, 0); + }); + + 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); + assert.strictEqual(safeSelfSymbols(page).length, 0); + }); }); diff --git a/ubo.js b/ubo.js index a624943..cc4f57c 100644 --- a/ubo.js +++ b/ubo.js @@ -20,15 +20,8 @@ Home: https://github.com/gorhill/uBlock */ -const scriptlets = {}; - - -scriptlets['abort-current-script.js'] = { -aliases: ["abort-current-inline-script.js","acis.js","acs.js"], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -function runAtHtmlElementFn(fn) { +const functions = {}; +functions['runAtHtmlElementFn'] = function runAtHtmlElementFn(fn) { if ( document.documentElement ) { fn(); return; @@ -38,8 +31,8 @@ function runAtHtmlElementFn(fn) { fn(); }); observer.observe(document, { childList: true }); -} -function trapPropertyFn(propChain, handler, options = {}) { +}; +functions['trapPropertyFn'] = function trapPropertyFn(propChain, handler, options = {}) { if ( propChain === '' ) { return; } let owner = self; let prop = propChain; @@ -110,8 +103,8 @@ function trapPropertyFn(propChain, handler, options = {}) { } catch { } return entry.value; -} -function safeSelf() { +}; +functions['safeSelf'] = function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; } @@ -284,13 +277,13 @@ function safeSelf() { }; } return safe; -} -function getRandomTokenFn() { +}; +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); -} -function getExceptionTokenFn() { +}; +functions['getExceptionTokenFn'] = function getExceptionTokenFn() { const token = getRandomTokenFn(); const oe = self.onerror; self.onerror = function(msg, ...args) { @@ -300,8 +293,8 @@ function getExceptionTokenFn() { } }.bind(); return token; -} -function abortCurrentScriptFn( +}; +functions['abortCurrentScriptFn'] = function abortCurrentScriptFn( target = '', needle = '', context = '' @@ -362,31 +355,13 @@ function abortCurrentScriptFn( currentValue = a; } }, { canThrow: true }); -} -function abortCurrentScript(...args) { +}; +functions['abortCurrentScript'] = function abortCurrentScript(...args) { runAtHtmlElementFn(( ) => { abortCurrentScriptFn(...args); }); }; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -abortCurrentScript(...args); -}, -}; - - -scriptlets['set-attr.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -function runAt(fn, when) { +functions['runAt'] = function runAt(fn, when) { const intFromReadyState = state => { const targets = { 'loading': 1, 'asap': 1, @@ -413,8 +388,8 @@ function runAt(fn, when) { const safe = safeSelf(); const args = [ 'readystatechange', onStateChange, { capture: true } ]; safe.addEventListener.apply(document, args); -} -function setAttrFn( +}; +functions['setAttrFn'] = function setAttrFn( trusted = false, logPrefix, selector = '', @@ -482,182 +457,8 @@ function setAttrFn( observer.observe(root, { subtree: true, childList: true }); }; runAt(( ) => { start(); }, options.runAt || 'idle'); -} -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; -} -function setAttr( +}; +functions['setAttr'] = function setAttr( selector = '', attr = '', value = '', @@ -678,2435 +479,2140 @@ function setAttr( const options = safe.parseVarargs(varargs); setAttrFn(false, logPrefix, selector, attr, value, options); }; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -setAttr(...args); -}, -}; - - -scriptlets['trusted-set-attr.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -func: function (scriptletGlobals = {}, ...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, +functions['trustedSetAttr'] = function trustedSetAttr( selector = '', attr = '', value = '', - options = {} + ...varargs ) { - 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; + 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}'`); } - 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}"`); + }; + const rmattr = nodes => { + for ( const node of nodes ?? document.querySelectorAll(selector) ) { + rmattrFromNode(node); } - 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 ) { + 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; } - shouldWork = true; - break; + 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); + } } - if ( shouldWork ) { break; } } - if ( shouldWork === false ) { return; } - timer = self.requestAnimationFrame(( ) => { - timer = undefined; - applySetAttr(); - }); }; - + 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 = ( ) => { - 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 }); + 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(); }, options.runAt || 'idle'); -} -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; + 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 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, - }; + 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'); + } } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + } + 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; } - 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); + // 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; } - try { - return new RegExp(match[1], match[2] || undefined); + // Enforce https + if ( c0 === 0x2B && step === '+https' ) { // + + const s = urlin.replace(/^https?:\/\//, ''); + if ( /^[\w-]:\/\//.test(s) ) { return; } + urlout = `https://${s}`; + continue; } - catch { + // 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; + } } - 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 }); + // 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; } - 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; + // 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; } - }; - bc.postMessage('areyouready?'); + // 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 { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; } - return safe; -} -function trustedSetAttr( +}; +functions['hrefSanitizer'] = function hrefSanitizer( selector = '', - attr = '', - value = '', - ...varargs + source = '' ) { + if ( typeof selector !== 'string' ) { return; } + if ( selector === '' ) { return; } 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); + 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'); }; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); +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 { } -} catch { -} -trustedSetAttr(...args); -}, }; - - -scriptlets['remove-attr.js'] = { -aliases: ["ra.js"], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; +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 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 fn = context[prop]; + if ( typeof fn !== 'function' ) { return; } + if ( proxyApplyFn.CtorContext === undefined ) { + proxyApplyFn.ctorContexts = []; + proxyApplyFn.CtorContext = class { + constructor(...args) { + this.init(...args); } - const expect = (options.canNegate !== true || pattern.startsWith('!') === false); - if ( expect === false ) { - pattern = pattern.slice(1); + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; } - const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); - if ( match !== null ) { - return { - re: new this.RegExp( - match[1], - match[2] || options.flags - ), - expect, - }; + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; } - if ( options.flags !== undefined ) { - return { - re: new this.RegExp(this.escapeRegexChars(pattern), - options.flags - ), - expect, - }; + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + }; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); } - 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); + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; } - try { - return new RegExp(match[1], match[2] || undefined); + 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; } - catch { + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); } - 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; } + }; + 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)); } - 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 }); + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); }; - 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; + } + 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); } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = this.v2; + this.#compiled = r; } - return safe; -} -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]; + 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]; + } } - return 0; - }; - const runAt = intFromReadyState(when); - if ( intFromReadyState(document.readyState) >= runAt ) { - fn(); return; + const result = this.#root['$'] ?? null; + this.#root = null; + return result; } - 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 onIdleFn(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); + dump() { + return JSON.stringify(this.#compiled); } - return self.requestAnimationFrame(fn); -} -function offIdleFn(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) } - return self.cancelAnimationFrame(id); -} -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}`); + get [Symbol.toStringTag]() { + return 'JSONPath'; } - const rmattrFromNode = node => { - for ( const attr of tokens ) { - if ( node.hasAttribute(attr) === false ) { continue; } - node.removeAttribute(attr); - safe.uboLog(logPrefix, `Removed attribute '${attr}'`); + #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 }); } - }; - const rmattr = nodes => { - for ( const node of nodes ?? document.querySelectorAll(selector) ) { - rmattrFromNode(node); + 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; } - }; - 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 ( 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; } } - if ( node.childElementCount ) { - rmattr(node.querySelectorAll(selector)); + 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 ( lazily ) { return; } - for ( const node of removedNodes ) { - if ( node.nodeType !== 1 ) { continue; } - if ( node.matches(selector) ) { - rmattrFromNode(node); + 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 ]); } } } - }; - const stop = ( ) => { - if ( start.observer ) { - start.observer.disconnect(); - start.observer = undefined; + 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 ( rmAttrLazily.timer ) { - offIdleFn(rmAttrLazily.timer); - rmAttrLazily.timer = undefined; + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, 'Quitting'); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); } - }; - const start = ( ) => { - rmattr(); - if ( /\bstay\b/.test(behavior) === false ) { - if ( options.quitAfter === undefined ) { return; } + 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; } - 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'); + 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; } } - }; - runAt(( ) => { start(); }, safe.String_split.call(behavior, /\s+/)); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); } -} catch { -} -removeAttr(...args); -}, -}; - - -scriptlets['trusted-create-html.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -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, + #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; } - 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); + if ( c0 === 0x2C /* , */ ) { + if ( needIdentifier ) { return; } + i += 1; + needIdentifier = true; + continue; } - try { - return new RegExp(match[1], match[2] || undefined); + 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; } - catch { + 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; } - 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; } + if ( this.v2 ) { return; } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } - 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; + 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; } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; + 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 }; } - return safe; -} -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]; + #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; } - return 0; - }; - const runAt = intFromReadyState(when); - if ( intFromReadyState(document.readyState) >= runAt ) { - fn(); return; + 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; } - 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 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; + #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] }; } - 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)); + #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; } - if ( toAppend.length === 0 ) { return; } - const toRemove = []; - const remove = ( ) => { - for ( const node of toRemove ) { - if ( node.parentNode === null ) { continue; } - node.parentNode.removeChild(node); + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); } - 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); + 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; } - }; - 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))); + 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; } - 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'); - } + 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; } - observer.observe(document, observerOptions); - }; - runAt(start, extraArgs.runAt || 'loading'); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); } -} catch { -} -trustedCreateHTML(...args); -}, }; - - -scriptlets['href-sanitizer.js'] = { -aliases: ["urlskip.js"], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -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 { - } -} -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}`); - }; +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'); } - return safe; -} -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]; + 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 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); - }; + 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 args = [ 'readystatechange', onStateChange, { capture: true } ]; - safe.addEventListener.apply(document, args); -} -function onIdleFn(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); + 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'); } - return self.requestAnimationFrame(fn); -} -function hrefSanitizer( - selector = '', - source = '' + 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 ( typeof selector !== 'string' ) { return; } - if ( selector === '' ) { return; } + if ( propChain === '' ) { 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); + 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; } - return elems.length; + if ( args.length < -argPos ) { return; } + return args.length + argPos; }; - const validateURL = text => { - if ( typeof text !== 'string' ) { return ''; } - if ( text === '' ) { return ''; } - if ( /[\x00-\x20\x7f]/.test(text) ) { return ''; } + const editObj = obj => { + let clone; try { - const url = new URL(text, document.location); - return url.href; + clone = safe.JSON_parse(safe.JSON_stringify(obj)); } 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}`); + 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 true; + return objAfter; }; - 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; + proxyApplyFn(propChain, function(context) { + const i = getArgPos(context.callArgs); + if ( i !== undefined ) { + const obj = editObj(context.callArgs[i]); + if ( obj ) { + context.callArgs[i] = obj; } - 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'); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -hrefSanitizer(...args); -}, + return context.reflect(); + }); }; - - -scriptlets['proxy-apply-config.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { - -function proxyApplyConfig(config = '') { - try { - if ( typeof proxyApplyFn !== 'function' ) { return; } - config = JSON.parse(config); - if ( typeof config !== 'object' ) { return; } - Object.assign(proxyApplyFn, config); - } catch { - } +functions['editInboundObject'] = function editInboundObject(propChain = '', argPos = '', jsonq = '') { + editInboundObjectFn(false, propChain, argPos, jsonq); }; -proxyApplyConfig(...args); -}, +functions['trustedEditInboundObject'] = function trustedEditInboundObject(propChain = '', argPos = '', jsonq = '') { + editInboundObjectFn(true, propChain, argPos, jsonq); }; - - -scriptlets['edit-outbound-object.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -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, - }; +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 { pattern, expect }; + return currentValue; }, - 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; + 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; }, - 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 { + set: function(a) { + currentValue = a; + if ( currentValue instanceof Object ) { + editObj(currentValue); } - 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; + }); + 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(); }; - 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 }); + 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; } - 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 ( self.chrome?.dom?.openOrClosedShadowRoot ) { // Chromium + return self.chrome.dom.openOrClosedShadowRoot(elem); } + return elem.shadowRoot; }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; -} -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); + lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { + if ( selector.startsWith('xpath:') === false ) { + return Array.from(context.querySelectorAll(selector)); } - init(callFn, callArgs) { - this.callFn = callFn; - this.callArgs = callArgs; - return this; + 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); + } } - reflect() { - const r = Reflect.construct(this.callFn, this.callArgs); - this.callFn = this.callArgs = this.private = undefined; - proxyApplyFn.ctorContexts.push(this); - return r; + return out; + } + lookupElementsFn.querySelectorEx = (selector, context = document) => { + const pos = selector.indexOf(' >>> '); + if ( pos === -1 ) { + return lookupElementsFn.queryOrEvaluateSelector(selector, context); } - static factory(...args) { - return proxyApplyFn.ctorContexts.length !== 0 - ? proxyApplyFn.ctorContexts.pop().init(...args) - : new proxyApplyFn.CtorContext(...args); + 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; }; - 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; + 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); } - 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; + if ( details.observer === undefined ) { + details.observer = new MutationObserver(( ) => { + lookupElementsFn.lookupAsync(details); + }); + details.observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }); } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); + if ( details.timer === undefined ) { + details.timer = onIdleFn(( ) => { + details.timer = undefined; + lookupElementsFn.lookupAsync(details); + }, { timeout: 151 }); } }; - 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); + if ( until === 0 ) { + return lookupElementsFn.lookup(directive); } - const proxyDetails = { - apply(target, thisArg, args) { - return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); + 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'); } }; - 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) { - 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; + 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)); + } } - 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]; + 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}`); } - 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'; + 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'); } - #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; + 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')}`); } - continue; + xhrInstances.set(this, xhrDetails); } - 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; + 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 ( 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 ( xhrDetails.response !== undefined ) { + return xhrDetails.response; } - 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; + let obj; + if ( typeof innerResponse === 'object' ) { + obj = innerResponse; + } else if ( typeof innerResponse === 'string' ) { + try { obj = safe.JSON_parse(innerResponse); } catch { } } - // 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 ( typeof obj !== 'object' || obj === null ) { + return (xhrDetails.response = innerResponse); } - if ( query.startsWith('[*]', i) ) { - mv ||= this.#CHILDREN; - steps.push({ mv, k: '*' }); - i += 3; - mv = this.#UNDEFINED; - continue; + const objAfter = jsonp.apply(obj); + if ( objAfter === undefined ) { + return (xhrDetails.response = innerResponse); } - 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; + safe.uboLog(logPrefix, 'Edited'); + const outerResponse = typeof innerResponse === 'string' + ? JSONPath.toJSON(objAfter, safe.JSON_stringify) + : objAfter; + return (xhrDetails.response = outerResponse); } - 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; - } + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; } - return resultset; + }; +}; +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'); } - #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 ]); + 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); } - 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); + send(body) { + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails ) { + body = this.#filterBody(body) || body; } - 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); + super.send(body); } - if ( k instanceof JSONPath.Regex ) { - const out = []; - for ( const key of JSONPath.keys(owner) ) { - if ( k.test(key) === false ) { continue; } - out.push(key); + #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 out; + return body; } - return [ k ]; + }; +}; +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]; + } + }; } - #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; } + 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); } - #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; + if ( typeof options === 'object' && options !== null ) { + collateFetchArgumentsFn.collateKnownProps(options, out); } - #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; + 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')}`); } - 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 }; + 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'); } - #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 }; + 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); } - #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; - } + 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')}`); } - 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; + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `After edit:\n${bodyAfter}`); } - 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; } + 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; } - 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 {}; } + const jsonBefore = match[1]; + let obj; + try { obj = safe.JSON_parse(jsonBefore); } catch { } + if ( typeof obj !== 'object' || obj === null ) { + linesAfter.push(lineBefore); + continue; } - 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; + const objAfter = jsonp.apply(obj); + if ( objAfter === undefined ) { + linesAfter.push(lineBefore); + continue; } - return hasOwn === target; + const jsonAfter = safe.JSON_stringify(objAfter); + const lineAfter = [ + lineBefore.slice(0, match.index), + jsonAfter, + lineBefore.slice(match.index + jsonBefore.length), + ].join(''); + linesAfter.push(lineAfter); } - #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; + 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); } - 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; + return super.open(method, url, ...args); } - 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 { } + get response() { + const innerResponse = super.response; + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails === undefined ) { + return innerResponse; } - if ( this.#compiled.re === null ) { return; } - obj[key] = lval.replace(this.#compiled.re, rval.replacement); - break; + 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); } - default: - break; + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; } - } -} -function editOutboundObjectFn( - trusted = false, - propChain = '', - jsonq = '' -) { - if ( propChain === '' ) { return; } + }; +}; +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-' : ''}edit-outbound-object`, - propChain, + `${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'); } - 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)}`); + 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 objAfter; + 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; + }); }); -} -function editOutboundObject(propChain = '', jsonq = '') { - editOutboundObjectFn(false, propChain, jsonq); }; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -editOutboundObject(...args); -}, +functions['jsonlEditFetchResponse'] = function jsonlEditFetchResponse(jsonq = '', ...args) { + jsonlEditFetchResponseFn(false, jsonq, ...args); }; - - -scriptlets['trusted-edit-outbound-object.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; +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()); } - 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; + 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); + } } - 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); + }); + }; + 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; + } } - try { - return new RegExp(match[1], match[2] || undefined); + 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'; } - catch { + } + } + 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); } - 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; } + 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; } - 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 }); + 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; } - 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; + 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; } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; + 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); } - return safe; -} -function proxyApplyFn( - target = '', - handler = '', - options = {} +}; +functions['trustedPruneInboundObject'] = function trustedPruneInboundObject( + entryPoint = '', + argPos = '', + rawPrunePaths = '', + rawNeedlePaths = '', + ...varargs ) { + if ( entryPoint === '' ) { return; } let context = globalThis; - let prop = target; + let prop = entryPoint; for (;;) { const pos = prop.indexOf('.'); if ( pos === -1 ) { break; } @@ -3114,705 +2620,15275 @@ function proxyApplyFn( 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 ( 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 ( proxyApplyFn.isCtor.has(target) === false ) { - proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); + if ( rawNeedlePaths !== '' ) { + needlePaths.push(...safe.String_split.call(rawNeedlePaths, / +/)); } - const proxyDetails = { - apply(target, thisArg, args) { - return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); + 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; }; - 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) { - 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); + 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; } - } else if ( query.startsWith('+=', r.i) ) { - r.modify = '+'; - val = query.slice(r.i+2); } - try { r.rval = JSON.parse(val); } - catch { return; } + 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)); } - 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 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)}`); } - 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; + 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')}`); } - // 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; + } + 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; } - continue; - } - if ( c === 0x3B /* ; */ ) { - if ( query.startsWith(';$', i) === false ) { return; } - steps.push( - { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, - { mv: this.#ROOT } + const objAfter = objectPruneFn( + objBefore, + rawPrunePaths, + rawNeedlePaths, + stackNeedle, + extraArgs ); - 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; + 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'; } - 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; + if ( outcome === 'match' ) { + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched optional "propsToMatch", "${extraArgs.propsToMatch}"`); } - 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; + xhrInstances.set(this, xhrDetails); } - 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; + return super.open(method, url, ...args); } - 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; + get response() { + const innerResponse = super.response; + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails === undefined ) { + return innerResponse; } - default: - break; + const responseLength = typeof innerResponse === 'string' + ? innerResponse.length + : undefined; + if ( xhrDetails.lastResponseLength !== responseLength ) { + xhrDetails.response = undefined; + xhrDetails.lastResponseLength = responseLength; } - } - 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; + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; } - 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 ]); + let objBefore; + if ( typeof innerResponse === 'object' ) { + objBefore = innerResponse; + } else if ( typeof innerResponse === 'string' ) { + try { + objBefore = safe.JSON_parse(innerResponse); + } catch { } } - 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 ]); - } + if ( typeof objBefore !== 'object' ) { + return (xhrDetails.response = innerResponse); } - } - 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); + 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 out; + return (xhrDetails.response = outerResponse); } - if ( typeof k === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - return [ k >= 0 ? k : owner.length + k ]; + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; } - if ( k === '*' ) { - if ( Array.isArray(owner) ) { return owner.keys(); } - return JSONPath.keys(owner); + }; +}; +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 ( k instanceof JSONPath.Regex ) { - const out = []; - for ( const key of JSONPath.keys(owner) ) { - if ( k.test(key) === false ) { continue; } - out.push(key); - } - return out; + if ( needle === '' || safe.logLevel > 1 ) { + safe.uboLog(logPrefix, 'Not prevented:\n', a); } - 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; } + 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))}`); } - } - #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() }); + 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 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; + 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 ( 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 }; + if ( matchesBoth && targetSelector !== undefined ) { + if ( elementMatches(thisArg) === false ) { return false; } } - 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; + 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]); } - end += 1; + } catch { } - 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; + 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)}`); } - 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 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); } } - 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 {}; } + if ( Object.hasOwn(window, 'addEventListener') ) { + proxyApplyFn('window.addEventListener', proxyFn); + if ( extraArgs.protect ) { protect(window); } } - 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; + }, 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 hasOwn === target; - } - #modifyVal(obj, key) { - let { modify, rval } = this.#compiled; - if ( typeof rval === 'string' ) { - rval = rval.replace('${now}', `${Date.now()}`); + return false; + }; + proxyApplyFn('eval', function(context) { + const a = context.callArgs[0]; + if ( !check(a) ) { + return context.reflect(); } - 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; + 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'); } - break; + code.style = styles.join(';'); + code.textContent = clipboardText; + html = htmlTemplate.replace('${warning}', + domAlert.replace('${text}', code.outerHTML) + ); + } else { + html = htmlTemplate.replace('${warning}', domAlert); } - 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; + 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(); } - 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 { } + }; + 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; } } - if ( this.#compiled.re === null ) { return; } - obj[key] = lval.replace(this.#compiled.re, rval.replacement); - break; + 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'); } - default: - break; + }; + 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); } -} -function editOutboundObjectFn( + 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, - propChain = '', - jsonq = '' + propsToMatch = '', + responseBody = '', + responseType = '', + ...varargs ) { - if ( propChain === '' ) { return; } const safe = safeSelf(); + const setTimeout = self.setTimeout; + const scriptletName = `${trusted ? 'trusted-' : ''}prevent-fetch`; const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-outbound-object`, - propChain, - jsonq + scriptletName, + propsToMatch, + responseBody, + responseType ); - 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(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(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)}`); + 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')}`); } - return objAfter; + 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; + }); }); -} -function trustedEditOutboundObject(propChain = '', jsonq = '') { - editOutboundObjectFn(true, propChain, jsonq); }; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedEditOutboundObject(...args); -}, +functions['preventFetch'] = function preventFetch(...args) { + preventFetchFn(false, ...args); }; - - -scriptlets['json-edit.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -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), +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'] = { +aliases: ["abort-current-inline-script.js","acis.js","acs.js"], + +requiresTrust: false, +fn: 'abortCurrentScript', +dependencies: ["runAtHtmlElementFn","trapPropertyFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","abortCurrentScriptFn"], +func: function (scriptletGlobals = {}, ...args) { +function runAtHtmlElementFn(fn) { + if ( document.documentElement ) { + fn(); + return; + } + const observer = new MutationObserver(( ) => { + observer.disconnect(); + fn(); + }); + observer.observe(document, { childList: true }); +} +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; +} +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; +} +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 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 }); +} +function abortCurrentScript(...args) { + runAtHtmlElementFn(( ) => { + abortCurrentScriptFn(...args); + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +abortCurrentScript(...args); +}, +}; + + +scriptlets['set-attr.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'setAttr', +dependencies: ["runAt","setAttrFn","safeSelf"], +func: function (scriptletGlobals = {}, ...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, + 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'); +} +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; +} +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); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +setAttr(...args); +}, +}; + + +scriptlets['trusted-set-attr.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedSetAttr', +dependencies: ["runAt","setAttrFn","safeSelf"], +func: function (scriptletGlobals = {}, ...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, + 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'); +} +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; +} +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); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +trustedSetAttr(...args); +}, +}; + + +scriptlets['remove-attr.js'] = { +aliases: ["ra.js"], + +requiresTrust: false, +fn: 'removeAttr', +dependencies: ["safeSelf","runAt","onIdleFn","offIdleFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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 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 = '', + 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+/)); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +removeAttr(...args); +}, +}; + + +scriptlets['trusted-create-html.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedCreateHTML', +dependencies: ["safeSelf","runAt"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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 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'); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +trustedCreateHTML(...args); +}, +}; + + +scriptlets['href-sanitizer.js'] = { +aliases: ["urlskip.js"], +world: 'ISOLATED', +requiresTrust: false, +fn: 'hrefSanitizer', +dependencies: ["urlSkip","safeSelf","runAt","onIdleFn"], +func: function (scriptletGlobals = {}, ...args) { +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 { + } +} +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; +} +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 onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); +} +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'); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +hrefSanitizer(...args); +}, +}; + + +scriptlets['proxy-apply-config.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'proxyApplyConfig', +dependencies: [], +func: function (scriptletGlobals = {}, ...args) { + +function proxyApplyConfig(config = '') { + try { + if ( typeof proxyApplyFn !== 'function' ) { return; } + config = JSON.parse(config); + if ( typeof config !== 'object' ) { return; } + Object.assign(proxyApplyFn, config); + } catch { + } +}; +proxyApplyConfig(...args); +}, +}; + + +scriptlets['edit-outbound-object.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editOutboundObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editOutboundObjectFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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; + }); +} +function editOutboundObject(propChain = '', jsonq = '') { + editOutboundObjectFn(false, propChain, jsonq); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +editOutboundObject(...args); +}, +}; + + +scriptlets['trusted-edit-outbound-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditOutboundObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editOutboundObjectFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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; + }); +} +function trustedEditOutboundObject(propChain = '', jsonq = '') { + editOutboundObjectFn(true, propChain, jsonq); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +trustedEditOutboundObject(...args); +}, +}; + + +scriptlets['json-edit.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonEdit', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","jsonEditFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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; + }); +} +function jsonEdit(jsonq = '', ...varargs) { + jsonEditFn(false, jsonq, ...varargs); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +jsonEdit(...args); +}, +}; + + +scriptlets['trusted-json-edit.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonEdit', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","jsonEditFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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; + }); +} +function trustedJsonEdit(jsonq = '', ...varargs) { + jsonEditFn(true, jsonq, ...varargs); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +trustedJsonEdit(...args); +}, +}; + + +scriptlets['edit-inbound-object.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editInboundObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editInboundObjectFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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(); + }); +} +function editInboundObject(propChain = '', argPos = '', jsonq = '') { + editInboundObjectFn(false, propChain, argPos, jsonq); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +editInboundObject(...args); +}, +}; + + +scriptlets['trusted-edit-inbound-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditInboundObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editInboundObjectFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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(); + }); +} +function trustedEditInboundObject(propChain = '', argPos = '', jsonq = '') { + editInboundObjectFn(true, propChain, argPos, jsonq); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +trustedEditInboundObject(...args); +}, +}; + + +scriptlets['edit-this-object.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editThisObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editThisObjectFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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; + }); +} +function editThisObject(...args) { + editThisObjectFn(false, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +editThisObject(...args); +}, +}; + + +scriptlets['trusted-edit-this-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditThisObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editThisObjectFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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; + }); +} +function trustedEditThisObject(...args) { + editThisObjectFn(true, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +trustedEditThisObject(...args); +}, +}; + + +scriptlets['edit-object-on-getter.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editObjectOnGetter', +dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnGetterFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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; + } + }); +} +function editObjectOnGetter(...args) { + editObjectOnGetterFn(false, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +editObjectOnGetter(...args); +}, +}; + + +scriptlets['trusted-edit-object-on-getter.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditObjectOnGetter', +dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnGetterFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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; + } + }); +} +function trustedEditObjectOnGetter(...args) { + editObjectOnGetterFn(true, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +trustedEditObjectOnGetter(...args); +}, +}; + + +scriptlets['edit-object-on-setter.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editObjectOnSetter', +dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnSetterFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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; +} +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; + } + } +} +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); + } +} +function editObjectOnSetter(...args) { + editObjectOnSetterFn(false, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +editObjectOnSetter(...args); +}, +}; + + +scriptlets['trusted-edit-object-on-setter.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditObjectOnSetter', +dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnSetterFn"], +func: function (scriptletGlobals = {}, ...args) { +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; +} +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), @@ -3968,104 +18044,6 @@ function safeSelf() { } return safe; } -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; -} class JSONPath { static create(query) { const jsonp = new JSONPath(); @@ -4614,34 +18592,46 @@ class JSONPath { } } } -function jsonEditFn(trusted = false, jsonq = '', ...varargs) { +function editObjectOnSetterFn( + trusted = false, + propChain = '', + jsonq = '', +) { + if ( propChain === '' ) { return; } const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit`, - jsonq, - ...varargs + `${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 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; } + const editObj = objBefore => { + const objAfter = jsonp.apply(objBefore); + if ( objAfter === undefined ) { return; } safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + 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); + } } - return objAfter; }); + if ( currentValue instanceof Object ) { + editObj(currentValue); + } } -function jsonEdit(jsonq = '', ...varargs) { - jsonEditFn(false, jsonq, ...varargs); +function trustedEditObjectOnSetter(...args) { + editObjectOnSetterFn(true, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -4651,16 +18641,33 @@ try { } } catch { } -jsonEdit(...args); +trustedEditObjectOnSetter(...args); }, }; -scriptlets['trusted-json-edit.js'] = { +scriptlets['edit-element-object.js'] = { aliases: [], -requiresTrust: true, +requiresTrust: false, +fn: 'editElementObject', +dependencies: ["sleepFn","safeSelf","onIdleFn","offIdleFn","lookupElementsFn","JSONPath","editElementObjectFn"], func: function (scriptletGlobals = {}, ...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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -4835,103 +18842,105 @@ function safeSelf() { } return safe; } -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); +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); } - 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; + return self.requestAnimationFrame(fn); +} +function offIdleFn(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); +} +function lookupElementsFn(directive, until = 0) { + if ( lookupElementsFn.querySelectorEx === undefined ) { + lookupElementsFn.getShadowRoot = elem => { + if ( elem.openOrClosedShadowRoot ) { // Firefox + return elem.openOrClosedShadowRoot; } - static factory(...args) { - return proxyApplyFn.ctorContexts.length !== 0 - ? proxyApplyFn.ctorContexts.pop().init(...args) - : new proxyApplyFn.CtorContext(...args); + if ( self.chrome?.dom?.openOrClosedShadowRoot ) { // Chromium + return self.chrome.dom.openOrClosedShadowRoot(elem); } + return elem.shadowRoot; }; - proxyApplyFn.applyContexts = []; - proxyApplyFn.ApplyContext = class { - constructor(...args) { - this.init(...args); + lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { + if ( selector.startsWith('xpath:') === false ) { + return Array.from(context.querySelectorAll(selector)); } - init(callFn, thisArg, callArgs) { - this.callFn = callFn; - this.thisArg = thisArg; - this.callArgs = callArgs; - return this; + 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); + } } - 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; + return out; + } + lookupElementsFn.querySelectorEx = (selector, context = document) => { + const pos = selector.indexOf(' >>> '); + if ( pos === -1 ) { + return lookupElementsFn.queryOrEvaluateSelector(selector, context); } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); + 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; }; - 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); + 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; } - }); - 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)); + 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 }); + } }; } - const proxiedTarget = new Proxy(fn, proxyDetails); - proxyApplyFn.proxies.set(proxiedTarget, fn); - context[prop] = proxiedTarget; + if ( until === 0 ) { + return lookupElementsFn.lookup(directive); + } + return new Promise(resolve => { + lookupElementsFn.lookupAsync({ directive, until, resolve }); + }); } class JSONPath { static create(query) { @@ -5481,34 +19490,43 @@ class JSONPath { } } } -function jsonEditFn(trusted = false, jsonq = '', ...varargs) { +async function editElementObjectFn( + trusted = false, + selector = '', + jsonq = '', + timeout = '' +) { + if ( selector === '' ) { return; } const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit`, - jsonq, - ...varargs + `${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 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)}`); + const process = elems => { + for ( const elem of elems ) { + const r = jsonp.apply(elem); + if ( r === undefined ) { continue; } + safe.uboLog(logPrefix, 'Edited'); } - return objAfter; - }); + }; + 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 ); } -function trustedJsonEdit(jsonq = '', ...varargs) { - jsonEditFn(true, jsonq, ...varargs); +function editElementObject(...args) { + editElementObjectFn(false, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -5518,16 +19536,33 @@ try { } } catch { } -trustedJsonEdit(...args); +editElementObject(...args); }, }; -scriptlets['edit-inbound-object.js'] = { +scriptlets['trusted-edit-element-object.js'] = { aliases: [], -requiresTrust: false, +requiresTrust: true, +fn: 'trustedEditElementObject', +dependencies: ["sleepFn","safeSelf","onIdleFn","offIdleFn","lookupElementsFn","JSONPath","editElementObjectFn"], func: function (scriptletGlobals = {}, ...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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -5702,103 +19737,105 @@ function safeSelf() { } return safe; } -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); +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); } - 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; + return self.requestAnimationFrame(fn); +} +function offIdleFn(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); +} +function lookupElementsFn(directive, until = 0) { + if ( lookupElementsFn.querySelectorEx === undefined ) { + lookupElementsFn.getShadowRoot = elem => { + if ( elem.openOrClosedShadowRoot ) { // Firefox + return elem.openOrClosedShadowRoot; } - static factory(...args) { - return proxyApplyFn.ctorContexts.length !== 0 - ? proxyApplyFn.ctorContexts.pop().init(...args) - : new proxyApplyFn.CtorContext(...args); + if ( self.chrome?.dom?.openOrClosedShadowRoot ) { // Chromium + return self.chrome.dom.openOrClosedShadowRoot(elem); } + return elem.shadowRoot; }; - proxyApplyFn.applyContexts = []; - proxyApplyFn.ApplyContext = class { - constructor(...args) { - this.init(...args); + lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { + if ( selector.startsWith('xpath:') === false ) { + return Array.from(context.querySelectorAll(selector)); } - init(callFn, thisArg, callArgs) { - this.callFn = callFn; - this.thisArg = thisArg; - this.callArgs = callArgs; - return this; + 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); + } } - 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; + return out; + } + lookupElementsFn.querySelectorEx = (selector, context = document) => { + const pos = selector.indexOf(' >>> '); + if ( pos === -1 ) { + return lookupElementsFn.queryOrEvaluateSelector(selector, context); } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); + 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; }; - 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); + 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; } - }); - 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)); + 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 }); + } }; } - const proxiedTarget = new Proxy(fn, proxyDetails); - proxyApplyFn.proxies.set(proxiedTarget, fn); - context[prop] = proxiedTarget; + if ( until === 0 ) { + return lookupElementsFn.lookup(directive); + } + return new Promise(resolve => { + lookupElementsFn.lookupAsync({ directive, until, resolve }); + }); } class JSONPath { static create(query) { @@ -6348,62 +20385,43 @@ class JSONPath { } } } -function editInboundObjectFn( +async function editElementObjectFn( trusted = false, - propChain = '', - argPosRaw = '', + selector = '', jsonq = '', + timeout = '' ) { - if ( propChain === '' ) { return; } + if ( selector === '' ) { return; } const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-inbound-object`, - propChain, - jsonq + `${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 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)}`); + const process = elems => { + for ( const elem of elems ) { + const r = jsonp.apply(elem); + if ( r === undefined ) { continue; } + safe.uboLog(logPrefix, 'Edited'); } - 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; - } + 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); } - return context.reflect(); - }); + await sleepFn(); + } while ( Date.now() < until ); } -function editInboundObject(propChain = '', argPos = '', jsonq = '') { - editInboundObjectFn(false, propChain, argPos, jsonq); +function trustedEditElementObject(...args) { + editElementObjectFn(true, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -6413,16 +20431,38 @@ try { } } catch { } -editInboundObject(...args); +trustedEditElementObject(...args); }, }; -scriptlets['trusted-edit-inbound-object.js'] = { +scriptlets['json-edit-xhr-response.js'] = { aliases: [], -requiresTrust: true, +requiresTrust: false, +fn: 'jsonEditXhrResponse', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrResponseFn"], func: function (scriptletGlobals = {}, ...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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -6597,103 +20637,24 @@ function safeSelf() { } return safe; } -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); +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; } } - }; - 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 ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); } - }; - 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; + return matched; } class JSONPath { static create(query) { @@ -7243,62 +21204,75 @@ class JSONPath { } } } -function editInboundObjectFn( - trusted = false, - propChain = '', - argPosRaw = '', - jsonq = '', -) { - if ( propChain === '' ) { return; } +function jsonEditXhrResponseFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-inbound-object`, - propChain, + `${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 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; + 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); } - if ( args.length < -argPos ) { return; } - return args.length + argPos; - }; - const editObj = obj => { - let clone; - try { - clone = safe.JSON_parse(safe.JSON_stringify(obj)); - } catch { + 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); } - 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)}`); + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; } - 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(); - }); } -function trustedEditInboundObject(propChain = '', argPos = '', jsonq = '') { - editInboundObjectFn(true, propChain, argPos, jsonq); +function jsonEditXhrResponse(jsonq = '', ...args) { + jsonEditXhrResponseFn(false, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -7308,16 +21282,38 @@ try { } } catch { } -trustedEditInboundObject(...args); +jsonEditXhrResponse(...args); }, }; -scriptlets['edit-this-object.js'] = { +scriptlets['trusted-json-edit-xhr-response.js'] = { aliases: [], -requiresTrust: false, +requiresTrust: true, +fn: 'trustedJsonEditXhrResponse', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrResponseFn"], func: function (scriptletGlobals = {}, ...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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -7492,103 +21488,24 @@ function safeSelf() { } return safe; } -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); +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; } } - }; - 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 ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); } - }; - 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; + return matched; } class JSONPath { static create(query) { @@ -8138,45 +22055,75 @@ class JSONPath { } } } -function editThisObjectFn( - trusted = false, - propChain = '', - jsonq = '', - order = '' -) { - if ( propChain === '' ) { return; } +function jsonEditXhrResponseFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-this-object`, - propChain, + `${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 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(); + 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); } - editObj(thisArg); - if ( order !== 'after' ) { - r = context.reflect(); + 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); } - return r; - }); + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; + } + }; } -function editThisObject(...args) { - editThisObjectFn(false, ...args); +function trustedJsonEditXhrResponse(jsonq = '', ...args) { + jsonEditXhrResponseFn(true, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -8186,16 +22133,38 @@ try { } } catch { } -editThisObject(...args); +trustedJsonEditXhrResponse(...args); }, }; -scriptlets['trusted-edit-this-object.js'] = { +scriptlets['json-edit-xhr-request.js'] = { aliases: [], -requiresTrust: true, +requiresTrust: false, +fn: 'jsonEditXhrRequest', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrRequestFn"], func: function (scriptletGlobals = {}, ...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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -8370,103 +22339,24 @@ function safeSelf() { } return safe; } -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); +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; } } - }; - 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 ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); } } - 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; + return matched; } class JSONPath { static create(query) { @@ -9016,45 +22906,58 @@ class JSONPath { } } } -function editThisObjectFn( - trusted = false, - propChain = '', - jsonq = '', - order = '' -) { - if ( propChain === '' ) { return; } +function jsonEditXhrRequestFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-this-object`, - propChain, + `${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 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(); + 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); } - editObj(thisArg); - if ( order !== 'after' ) { - r = context.reflect(); + send(body) { + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails ) { + body = this.#filterBody(body) || body; + } + super.send(body); } - return r; - }); + #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; + } + }; } -function trustedEditThisObject(...args) { - editThisObjectFn(true, ...args); +function jsonEditXhrRequest(jsonq = '', ...args) { + jsonEditXhrRequestFn(false, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -9064,87 +22967,37 @@ try { } } catch { } -trustedEditThisObject(...args); +jsonEditXhrRequest(...args); }, }; -scriptlets['edit-object-on-getter.js'] = { +scriptlets['trusted-json-edit-xhr-request.js'] = { aliases: [], -requiresTrust: false, +requiresTrust: true, +fn: 'trustedJsonEditXhrRequest', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrRequestFn"], func: function (scriptletGlobals = {}, ...args) { -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); - } +function parsePropertiesToMatchFn(propsToMatch, implicit = '') { 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); + 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)); } } - try { - safe.Object_defineProperty(owner, prop, { - get() { - return trapPropertyFn.getter(owner, prop); - }, - set(value) { - trapPropertyFn.setter(owner, prop, value); - } - }); - } catch { - } - return entry.value; + return needles; } function safeSelf() { if ( safeSelf.safe ) { @@ -9320,6 +23173,25 @@ function safeSelf() { } return safe; } +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; +} class JSONPath { static create(query) { const jsonp = new JSONPath(); @@ -9868,43 +23740,58 @@ class JSONPath { } } } -function editObjectOnGetterFn( - trusted = false, - propChain = '', - jsonq = '', -) { - if ( propChain === '' ) { return; } +function jsonEditXhrRequestFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-object-on-getter`, - propChain, + `${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 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); + 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; } - return currentValue; - }, - set: function(a) { - currentValue = a; + 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; + } + }; } -function editObjectOnGetter(...args) { - editObjectOnGetterFn(false, ...args); +function trustedJsonEditXhrRequest(jsonq = '', ...args) { + jsonEditXhrRequestFn(true, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -9914,261 +23801,154 @@ try { } } catch { } -editObjectOnGetter(...args); +trustedJsonEditXhrRequest(...args); }, }; -scriptlets['trusted-edit-object-on-getter.js'] = { +scriptlets['json-edit-fetch-response.js'] = { aliases: [], -requiresTrust: true, +requiresTrust: false, +fn: 'jsonEditFetchResponse', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchResponseFn"], func: function (scriptletGlobals = {}, ...args) { -function trapPropertyFn(propChain, handler, options = {}) { - if ( propChain === '' ) { return; } - let owner = self; - let prop = propChain; +function proxyApplyFn( + target = '', + handler = '', + options = {} +) { + let context = globalThis; + let prop = target; 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; -} -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); } - 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 fn = context[prop]; + if ( typeof fn !== 'function' ) { return; } + if ( proxyApplyFn.CtorContext === undefined ) { + proxyApplyFn.ctorContexts = []; + proxyApplyFn.CtorContext = class { + constructor(...args) { + this.init(...args); } - const expect = (options.canNegate !== true || pattern.startsWith('!') === false); - if ( expect === false ) { - pattern = pattern.slice(1); + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; } - const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); - if ( match !== null ) { - return { - re: new this.RegExp( - match[1], - match[2] || options.flags - ), - expect, - }; + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; } - if ( options.flags !== undefined ) { - return { - re: new this.RegExp(this.escapeRegexChars(pattern), - options.flags - ), - expect, - }; + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + }; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); } - 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); + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; } - try { - return new RegExp(match[1], match[2] || undefined); + 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; } - catch { + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); } - 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; } + }; + 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)); } - 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 }); + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); }; - 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; + } + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +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; } } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; + if ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); + } } - return safe; + return matched; } class JSONPath { static create(query) { @@ -10718,134 +24498,6 @@ class JSONPath { } } } -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; - } - }); -} -function trustedEditObjectOnGetter(...args) { - editObjectOnGetterFn(true, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedEditObjectOnGetter(...args); -}, -}; - - -scriptlets['edit-object-on-setter.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -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; -} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -11020,6 +24672,252 @@ function safeSelf() { } return safe; } +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( + `${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; + }); + }); +} +function jsonEditFetchResponse(jsonq = '', ...args) { + jsonEditFetchResponseFn(false, jsonq, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +jsonEditFetchResponse(...args); +}, +}; + + +scriptlets['trusted-json-edit-fetch-response.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonEditFetchResponse', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchResponseFn"], +func: function (scriptletGlobals = {}, ...args) { +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 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; +} class JSONPath { static create(query) { const jsonp = new JSONPath(); @@ -11568,137 +25466,6 @@ class JSONPath { } } } -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); - } -} -function editObjectOnSetter(...args) { - editObjectOnSetterFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -editObjectOnSetter(...args); -}, -}; - - -scriptlets['trusted-edit-object-on-setter.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -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; -} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -11873,6 +25640,252 @@ function safeSelf() { } return safe; } +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( + `${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; + }); + }); +} +function trustedJsonEditFetchResponse(jsonq = '', ...args) { + jsonEditFetchResponseFn(true, jsonq, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +trustedJsonEditFetchResponse(...args); +}, +}; + + +scriptlets['json-edit-fetch-request.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonEditFetchRequest', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchRequestFn"], +func: function (scriptletGlobals = {}, ...args) { +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 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; +} class JSONPath { static create(query) { const jsonp = new JSONPath(); @@ -12421,80 +26434,6 @@ class JSONPath { } } } -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); - } -} -function trustedEditObjectOnSetter(...args) { - editObjectOnSetterFn(true, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedEditObjectOnSetter(...args); -}, -}; - - -scriptlets['edit-element-object.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -12669,105 +26608,248 @@ function safeSelf() { } return safe; } -function onIdleFn(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); +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]; + } + }; } - return self.requestAnimationFrame(fn); + 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 offIdleFn(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); +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'); } - return self.cancelAnimationFrame(id); + 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); } -function lookupElementsFn(directive, until = 0) { - if ( lookupElementsFn.querySelectorEx === undefined ) { - lookupElementsFn.getShadowRoot = elem => { - if ( elem.openOrClosedShadowRoot ) { // Firefox - return elem.openOrClosedShadowRoot; +function jsonEditFetchRequest(jsonq = '', ...args) { + jsonEditFetchRequestFn(false, jsonq, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +jsonEditFetchRequest(...args); +}, +}; + + +scriptlets['trusted-json-edit-fetch-request.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonEditFetchRequest', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchRequestFn"], +func: function (scriptletGlobals = {}, ...args) { +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); } - if ( self.chrome?.dom?.openOrClosedShadowRoot ) { // Chromium - return self.chrome.dom.openOrClosedShadowRoot(elem); + 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); } - return elem.shadowRoot; }; - lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { - if ( selector.startsWith('xpath:') === false ) { - return Array.from(context.querySelectorAll(selector)); + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); } - 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); - } + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; } - return out; - } - lookupElementsFn.querySelectorEx = (selector, context = document) => { - const pos = selector.indexOf(' >>> '); - if ( pos === -1 ) { - return lookupElementsFn.queryOrEvaluateSelector(selector, context); + 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; } - 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)); + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); } - 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; + 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); } - 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 }); - } + }); + 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)); }; } - if ( until === 0 ) { - return lookupElementsFn.lookup(directive); + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +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 new Promise(resolve => { - lookupElementsFn.lookupAsync({ directive, until, resolve }); - }); + 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; } class JSONPath { static create(query) { @@ -13317,43 +27399,265 @@ class JSONPath { } } } -async function editElementObjectFn( - trusted = false, - selector = '', - jsonq = '', - timeout = '' -) { - if ( selector === '' ) { 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}`); + }; + } + return safe; +} +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 jsonEditFetchRequestFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-element-object`, - selector, jsonq, timeout + `${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 process = elems => { - for ( const elem of elems ) { - const r = jsonp.apply(elem); - if ( r === undefined ) { continue; } - safe.uboLog(logPrefix, 'Edited'); + 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(); } - }; - 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); + 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')}`); + } } - await sleepFn(); - } while ( Date.now() < until ); + 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); } -function editElementObject(...args) { - editElementObjectFn(false, ...args); +function trustedJsonEditFetchRequest(jsonq = '', ...args) { + jsonEditFetchRequestFn(true, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -13363,30 +27667,56 @@ try { } } catch { } -editElementObject(...args); +trustedJsonEditFetchRequest(...args); }, }; -scriptlets['trusted-edit-element-object.js'] = { +scriptlets['jsonl-edit-xhr-response.js'] = { aliases: [], -requiresTrust: true, +requiresTrust: false, +fn: 'jsonlEditXhrResponse', +dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","jsonlEditFn","JSONPath","jsonlEditXhrResponseFn"], func: function (scriptletGlobals = {}, ...args) { -function sleepFn(ms = 0) { - const nap = ( ) => { - return new Promise(resolve => { - self.requestAnimationFrame(resolve); - }); +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)); + } } - const until = Date.now() + ms; - const sleep = async resolve => { - do { - await nap(); - } while ( Date.now() < until ); - resolve(); - }; - return new Promise(resolve => { sleep(resolve); }); + 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 safeSelf() { if ( safeSelf.safe ) { @@ -13562,105 +27892,38 @@ function safeSelf() { } return safe; } -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 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; +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; } - 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); + 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 new Promise(resolve => { - lookupElementsFn.lookupAsync({ directive, until, resolve }); - }); + return linesAfter.join(lineSeparator); } class JSONPath { static create(query) { @@ -14210,43 +28473,70 @@ class JSONPath { } } } -async function editElementObjectFn( - trusted = false, - selector = '', - jsonq = '', - timeout = '' -) { - if ( selector === '' ) { return; } +function jsonlEditXhrResponseFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-element-object`, - selector, jsonq, timeout + `${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 process = elems => { - for ( const elem of elems ) { - const r = jsonp.apply(elem); - if ( r === undefined ) { continue; } - safe.uboLog(logPrefix, 'Edited'); + 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); } - }; - 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); + 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); } - await sleepFn(); - } while ( Date.now() < until ); + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; + } + }; } -function trustedEditElementObject(...args) { - editElementObjectFn(true, ...args); +function jsonlEditXhrResponse(jsonq = '', ...args) { + jsonlEditXhrResponseFn(false, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -14256,15 +28546,17 @@ try { } } catch { } -trustedEditElementObject(...args); +jsonlEditXhrResponse(...args); }, }; -scriptlets['json-edit-xhr-response.js'] = { +scriptlets['trusted-jsonl-edit-xhr-response.js'] = { aliases: [], -requiresTrust: false, +requiresTrust: true, +fn: 'trustedJsonlEditXhrResponse', +dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","jsonlEditFn","JSONPath","jsonlEditXhrResponseFn"], func: function (scriptletGlobals = {}, ...args) { function parsePropertiesToMatchFn(propsToMatch, implicit = '') { const safe = safeSelf(); @@ -14286,6 +28578,25 @@ function parsePropertiesToMatchFn(propsToMatch, implicit = '') { } 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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -14460,24 +28771,38 @@ function safeSelf() { } return safe; } -function matchObjectPropertiesFn(propNeedles, ...objs) { +function jsonlEditFn(jsonp, text = '', jsonExtract) { 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}`); + 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 matched; + return linesAfter.join(lineSeparator); } class JSONPath { static create(query) { @@ -15027,10 +29352,10 @@ class JSONPath { } } } -function jsonEditXhrResponseFn(trusted, jsonq = '', ...varargs) { +function jsonlEditXhrResponseFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-xhr-response`, + `${trusted ? 'trusted-' : ''}jsonl-edit-xhr-response`, jsonq ); const xhrInstances = new WeakMap(); @@ -15040,6 +29365,9 @@ function jsonEditXhrResponseFn(trusted, jsonq = '', ...varargs) { } 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 }; @@ -15056,7 +29384,9 @@ function jsonEditXhrResponseFn(trusted, jsonq = '', ...varargs) { get response() { const innerResponse = super.response; const xhrDetails = xhrInstances.get(this); - if ( xhrDetails === undefined ) { return innerResponse; } + if ( xhrDetails === undefined ) { + return innerResponse; + } const responseLength = typeof innerResponse === 'string' ? innerResponse.length : undefined; @@ -15067,23 +29397,13 @@ function jsonEditXhrResponseFn(trusted, jsonq = '', ...varargs) { 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 ) { + if ( typeof innerResponse !== 'string' ) { return (xhrDetails.response = innerResponse); } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { - return (xhrDetails.response = innerResponse); + const outerResponse = jsonlEditFn(jsonp, innerResponse, jsonExtract); + if ( outerResponse !== innerResponse ) { + safe.uboLog(logPrefix, 'Pruned'); } - safe.uboLog(logPrefix, 'Edited'); - const outerResponse = typeof innerResponse === 'string' - ? JSONPath.toJSON(objAfter, safe.JSON_stringify) - : objAfter; return (xhrDetails.response = outerResponse); } get responseText() { @@ -15094,8 +29414,8 @@ function jsonEditXhrResponseFn(trusted, jsonq = '', ...varargs) { } }; } -function jsonEditXhrResponse(jsonq = '', ...args) { - jsonEditXhrResponseFn(false, jsonq, ...args); +function trustedJsonlEditXhrResponse(jsonq = '', ...args) { + jsonlEditXhrResponseFn(true, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -15105,16 +29425,116 @@ try { } } catch { } -jsonEditXhrResponse(...args); +trustedJsonlEditXhrResponse(...args); }, }; -scriptlets['trusted-json-edit-xhr-response.js'] = { +scriptlets['jsonl-edit-fetch-response.js'] = { aliases: [], -requiresTrust: true, +requiresTrust: false, +fn: 'jsonlEditFetchResponse', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","jsonlEditFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonlEditFetchResponseFn"], func: function (scriptletGlobals = {}, ...args) { +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 parsePropertiesToMatchFn(propsToMatch, implicit = '') { const safe = safeSelf(); const needles = new Map(); @@ -15135,180 +29555,6 @@ function parsePropertiesToMatchFn(propsToMatch, implicit = '') { } return needles; } -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; -} function matchObjectPropertiesFn(propNeedles, ...objs) { const safe = safeSelf(); const matched = []; @@ -15328,6 +29574,39 @@ function matchObjectPropertiesFn(propNeedles, ...objs) { } return matched; } +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); +} class JSONPath { static create(query) { const jsonp = new JSONPath(); @@ -15876,114 +30155,6 @@ class JSONPath { } } } -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; - } - }; -} -function trustedJsonEditXhrResponse(jsonq = '', ...args) { - jsonEditXhrResponseFn(true, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonEditXhrResponse(...args); -}, -}; - - -scriptlets['json-edit-xhr-request.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -16084,79 +30255,314 @@ function safeSelf() { try { return new RegExp(match[1], match[2] || undefined); } - catch { + 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; +} +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 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; + }); + }); +} +function jsonlEditFetchResponse(jsonq = '', ...args) { + jsonlEditFetchResponseFn(false, jsonq, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +jsonlEditFetchResponse(...args); +}, +}; + + +scriptlets['trusted-jsonl-edit-fetch-response.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonlEditFetchResponse', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","jsonlEditFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonlEditFetchResponseFn"], +func: function (scriptletGlobals = {}, ...args) { +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; } - 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 }); + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); } - 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; + 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); } }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); + 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)); }; } - return safe; + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +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(); @@ -16177,6 +30583,39 @@ function matchObjectPropertiesFn(propNeedles, ...objs) { } return matched; } +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); +} class JSONPath { static create(query) { const jsonp = new JSONPath(); @@ -16725,58 +31164,276 @@ class JSONPath { } } } -function jsonEditXhrRequestFn(trusted, jsonq = '', ...varargs) { +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; +} +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 jsonlEditFetchResponseFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-xhr-request`, + `${trusted ? 'trusted-' : ''}jsonl-edit-fetch-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); - } - 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'); + 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, `After edit:\n${body}`); + safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); } - return body; } - }; + 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; + }); + }); } -function jsonEditXhrRequest(jsonq = '', ...args) { - jsonEditXhrRequestFn(false, jsonq, ...args); +function trustedJsonlEditFetchResponse(jsonq = '', ...args) { + jsonlEditFetchResponseFn(true, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -16786,35 +31443,61 @@ try { } } catch { } -jsonEditXhrRequest(...args); +trustedJsonlEditFetchResponse(...args); }, }; -scriptlets['trusted-json-edit-xhr-request.js'] = { -aliases: [], +scriptlets['abort-on-stack-trace.js'] = { +aliases: ["aost.js"], -requiresTrust: true, +requiresTrust: false, +fn: 'abortOnStackTrace', +dependencies: ["matchesStackTraceFn","safeSelf","getRandomTokenFn","getExceptionTokenFn"], func: function (scriptletGlobals = {}, ...args) { -function parsePropertiesToMatchFn(propsToMatch, implicit = '') { +function matchesStackTraceFn( + needleDetails, + logLevel = '' +) { 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)); + 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()); } - return needles; + 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; } function safeSelf() { if ( safeSelf.safe ) { @@ -16990,625 +31673,502 @@ function safeSelf() { } return safe; } -function matchObjectPropertiesFn(propNeedles, ...objs) { +function getRandomTokenFn() { 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 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); } - } - return matched; + }.bind(); + return token; } -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); +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); } - } 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; + }); + }; + const owner = window; + makeProxy(owner, chain); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - 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]; - } +} catch { +} +abortOnStackTrace(...args); +}, +}; + + +scriptlets['trusted-prune-inbound-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedPruneInboundObject', +dependencies: ["getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","safeSelf","objectFindOwnerFn"], +func: function (scriptletGlobals = {}, ...args) { +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); } - 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) + }.bind(); + return token; +} +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()); } - get [Symbol.toStringTag]() { - return 'JSONPath'; + 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')); } - #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 }); + return r; +} +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; } - 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; + } + if ( objectPruneFn.mustProcess === undefined ) { + objectPruneFn.mustProcess = (root, needlePaths) => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; } - 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; + 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'; } - 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; + if ( outcome === 'match' ) { return obj; } +} +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 }; } - case this.#QUANTIFIER: { - const { length } = resultset; - if ( length < step.min || length > step.max ) { return []; } - resultset = []; - break; + 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, + }; } - default: - break; + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; } - } - 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; + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test(details.re, haystack) === details.expect; } - 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 ]); - } + 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); } - 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 ]); - } + try { + return new RegExp(match[1], match[2] || undefined); } - } - 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); + catch { } - 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); + 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; } } - if ( k instanceof JSONPath.Regex ) { - const out = []; - for ( const key of JSONPath.keys(owner) ) { - if ( k.test(key) === false ) { continue; } - out.push(key); + 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 }); } - 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; }, + bcBuffer.push({ type, text }); }; - 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; + 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 ( 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 }; + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; } - #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; + return safe; +} +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); } - 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; + 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; } - 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()}`); + return modified; } - 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; + 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; } - 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; + return found; } - 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 ( prop === '{-}' && owner instanceof Object ) { + for ( const key of Object.keys(owner) ) { + if ( objectFindOwnerFn(owner[key], next) === false ) { continue; } + delete owner[key]; + found = true; } - if ( this.#compiled.re === null ) { return; } - obj[key] = lval.replace(this.#compiled.re, rval.replacement); - break; + return found; } - default: - break; + 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); } } -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'); +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 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}`); + 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 body; } + return true; }; -} -function trustedJsonEditXhrRequest(jsonq = '', ...args) { - jsonEditXhrRequestFn(true, jsonq, ...args); + 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); + }, + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -17618,15 +32178,17 @@ try { } } catch { } -trustedJsonEditXhrRequest(...args); +trustedPruneInboundObject(...args); }, }; -scriptlets['json-edit-fetch-response.js'] = { +scriptlets['trusted-prune-outbound-object.js'] = { aliases: [], -requiresTrust: false, +requiresTrust: true, +fn: 'trustedPruneOutboundObject', +dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', @@ -17676,641 +32238,118 @@ function proxyApplyFn( 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 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; -} -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; + return this; } - 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; + 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; } - 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; + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); } - 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; + }; + 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); } - } - 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; } + }); + proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); + Function.prototype.toString = proxiedToString; } - 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] }; + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); } - #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; + const proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); } - return hasOwn === target; + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; } - #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; + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +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); } - break; + 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; } - 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; + 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; } - 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 ( prop === '{-}' && owner instanceof Object ) { + for ( const key of Object.keys(owner) ) { + if ( objectFindOwnerFn(owner[key], next) === false ) { continue; } + delete owner[key]; + found = true; } - if ( this.#compiled.re === null ) { return; } - obj[key] = lval.replace(this.#compiled.re, rval.replacement); - break; + return found; } - default: - break; + 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); } } function safeSelf() { @@ -18487,94 +32526,128 @@ function safeSelf() { } return safe; } -function collateFetchArgumentsFn(resource, options) { +function getRandomTokenFn() { 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]; - } - }; + 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 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 ( - typeof resource !== 'object' || - safe.Object_toString.call(resource) !== '[object Request]' + logLevel === 'all' || + logLevel === 'match' && r || + logLevel === 'nomatch' && !r ) { - 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); + safe.uboLog(stack.replace(/\t/g, '\n')); } - return out; + return r; } -function jsonEditFetchResponseFn(trusted, jsonq = '', ...varargs) { +function objectPruneFn( + obj, + rawPrunePaths, + rawNeedlePaths, + stackNeedleDetails = { matchAll: true }, + extraArgs = {} +) { + if ( typeof rawPrunePaths !== 'string' ) { return; } 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 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; + } } - 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')}`); + 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'; } } - 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; - }); - }); + } + if ( outcome === 'match' ) { return obj; } } -function jsonEditFetchResponse(jsonq = '', ...args) { - jsonEditFetchResponseFn(false, jsonq, ...args); +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; + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -18584,15 +32657,17 @@ try { } } catch { } -jsonEditFetchResponse(...args); +trustedPruneOutboundObject(...args); }, }; -scriptlets['trusted-json-edit-fetch-response.js'] = { +scriptlets['json-prune.js'] = { aliases: [], -requiresTrust: true, +requiresTrust: false, +fn: 'jsonPrune', +dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', @@ -18675,609 +32750,615 @@ function proxyApplyFn( 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 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; -} -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; + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); } - get valid() { - return this.#compiled !== undefined; + 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)); + }; } - 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); + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +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 ( query.startsWith('+=', r.i) ) { - r.modify = '+'; - val = query.slice(r.i+2); + } else if ( safe.Object_hasOwn(owner, chain) ) { + delete owner[chain]; + modified = true; } - try { r.rval = JSON.parse(val); } - catch { return; } + return modified; } - 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 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; } - 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) + 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); } - get [Symbol.toStringTag]() { - return 'JSONPath'; +} +function safeSelf() { + if ( safeSelf.safe ) { + return safeSelf.safe; } - #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; + 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 }; } - // 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; + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); } - 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; + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; } - 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 ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; } - 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; + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test(details.re, haystack) === details.expect; } - // 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; + 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); } - if ( query.startsWith('[*]', i) ) { - mv ||= this.#CHILDREN; - steps.push({ mv, k: '*' }); - i += 3; - mv = this.#UNDEFINED; - continue; + try { + return new RegExp(match[1], match[2] || undefined); } - 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; + 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; } } - 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; + 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 }); } - case this.#QUANTIFIER: { - const { length } = resultset; - if ( length < step.min || length > step.max ) { return []; } - resultset = []; + 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; - } - default: + 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; +} +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 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; +} +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; } - 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 ]); + if ( objectPruneFn.mustProcess === undefined ) { + objectPruneFn.mustProcess = (root, needlePaths) => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; } } - } - return listout; + return true; + }; } - #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); + 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'; } - 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 ( outcome === 'match' ) { return obj; } +} +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)); } - 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; + 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 [ k ]; + return objAfter; + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - #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; } +} catch { +} +jsonPrune(...args); +}, +}; + + +scriptlets['json-prune-fetch-response.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonPruneFetchResponse', +dependencies: ["parsePropertiesToMatchFn","objectFindOwnerFn","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","matchObjectPropertiesFn","safeSelf","collateFetchArgumentsFn"], +func: function (scriptletGlobals = {}, ...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; } - } - #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() }); + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); } - 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; + return needles; +} +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); } - 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; + 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; } - if ( this.v2 ) { return; } - const r = this.#consumeUnquotedIdentifier(query, i); - if ( r === undefined ) { return; } - keys.push(r.s); - i = r.i; + return modified; } - 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 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; } - 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 ( prop === '{-}' && owner instanceof Object ) { + for ( const key of Object.keys(owner) ) { + if ( objectFindOwnerFn(owner[key], next) === false ) { continue; } + delete owner[key]; + found = true; } - 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; - } + 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; } - end += 1; + return found; } - return { s: parts.join(''), i: end }; + if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } + owner = owner[prop]; + chain = chain.slice(pos + 1); } - #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; +} +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); } - 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; } + }.bind(); + return token; +} +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'; } - return i + match[0].length - 1; + 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()); } - #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] }; + 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')); } - #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 r; +} +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; } - 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; + if ( objectPruneFn.mustProcess === undefined ) { + objectPruneFn.mustProcess = (root, needlePaths) => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; + } } - 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 { } + 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 ( this.#compiled.re === null ) { return; } - obj[key] = lval.replace(this.#compiled.re, rval.replacement); - break; } - default: - break; + } + if ( outcome === 'match' ) { return obj; } +} +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 safeSelf() { if ( safeSelf.safe ) { @@ -19487,21 +33568,19 @@ function collateFetchArgumentsFn(resource, options) { } return out; } -function jsonEditFetchResponseFn(trusted, jsonq = '', ...varargs) { +function jsonPruneFetchResponse( + rawPrunePaths = '', + rawNeedlePaths = '', + ...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 logPrefix = safe.makeLogPrefix('json-prune-fetch-response', rawPrunePaths, rawNeedlePaths); const extraArgs = safe.parseVarargs(varargs); const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - proxyApplyFn('fetch', function(context) { - const args = context.callArgs; - const fetchPromise = context.reflect(); + 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); @@ -19512,11 +33591,21 @@ function jsonEditFetchResponseFn(trusted, jsonq = '', ...varargs) { } 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'); + 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, @@ -19537,713 +33626,215 @@ function jsonEditFetchResponseFn(trusted, jsonq = '', ...varargs) { safe.uboErr(logPrefix, 'Error:', reason); return fetchPromise; }); + }; + self.fetch = new Proxy(self.fetch, { + apply: applyHandler }); -} -function trustedJsonEditFetchResponse(jsonq = '', ...args) { - jsonEditFetchResponseFn(true, jsonq, ...args); }; try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonEditFetchResponse(...args); -}, -}; - - -scriptlets['json-edit-fetch-request.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -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 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; -} -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 ]; + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - #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; } +} catch { +} +jsonPruneFetchResponse(...args); +}, +}; + + +scriptlets['json-prune-xhr-response.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonPruneXhrResponse', +dependencies: ["parsePropertiesToMatchFn","objectFindOwnerFn","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","safeSelf","matchObjectPropertiesFn"], +func: function (scriptletGlobals = {}, ...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; } - } - #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() }); + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); } - 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; + return needles; +} +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); } - 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; + 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; } - if ( this.v2 ) { return; } - const r = this.#consumeUnquotedIdentifier(query, i); - if ( r === undefined ) { return; } - keys.push(r.s); - i = r.i; + return modified; } - 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 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; } - 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 ( prop === '{-}' && owner instanceof Object ) { + for ( const key of Object.keys(owner) ) { + if ( objectFindOwnerFn(owner[key], next) === false ) { continue; } + delete owner[key]; + found = true; } - 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; - } + 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; } - end += 1; + return found; } - return { s: parts.join(''), i: end }; + if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } + owner = owner[prop]; + chain = chain.slice(pos + 1); } - #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; +} +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); } - 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; } + }.bind(); + return token; +} +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'; } - return i + match[0].length - 1; + 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()); } - #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] }; + 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')); } - #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 r; +} +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; } - 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; + if ( objectPruneFn.mustProcess === undefined ) { + objectPruneFn.mustProcess = (root, needlePaths) => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; + } } - 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 { } + 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 ( this.#compiled.re === null ) { return; } - obj[key] = lval.replace(this.#compiled.re, rval.replacement); - break; - } - default: - break; } } + if ( outcome === 'match' ) { return obj; } } function safeSelf() { if ( safeSelf.safe ) { @@ -20419,91 +34010,106 @@ function safeSelf() { } return safe; } -function collateFetchArgumentsFn(resource, options) { +function matchObjectPropertiesFn(propNeedles, ...objs) { 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]; + 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 ( - typeof resource !== 'object' || - safe.Object_toString.call(resource) !== '[object Request]' - ) { - out.url = `${resource}`; - } else { - let clone; - try { - clone = safe.Request_clone.call(resource); - } catch { + if ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); } - collateFetchArgumentsFn.collateKnownProps(clone || resource, out); - } - if ( typeof options === 'object' && options !== null ) { - collateFetchArgumentsFn.collateKnownProps(options, out); } - return out; + return matched; } -function jsonEditFetchRequestFn(trusted, jsonq = '', ...varargs) { +function jsonPruneXhrResponse( + rawPrunePaths = '', + rawNeedlePaths = '', + ...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 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 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(); + 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); } - 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')}`); + 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); } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${bodyAfter}`); + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; } - options.body = bodyAfter; - return context.reflect(); }; - proxyApplyFn('fetch', proxyHandler); - proxyApplyFn('Request', proxyHandler); -} -function jsonEditFetchRequest(jsonq = '', ...args) { - jsonEditFetchRequestFn(false, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -20513,15 +34119,17 @@ try { } } catch { } -jsonEditFetchRequest(...args); +jsonPruneXhrResponse(...args); }, }; -scriptlets['trusted-json-edit-fetch-request.js'] = { +scriptlets['evaldata-prune.js'] = { aliases: [], -requiresTrust: true, +requiresTrust: false, +fn: 'evaldataPrune', +dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', @@ -20554,658 +34162,135 @@ function proxyApplyFn( 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 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; -} -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; }, + return r; + } + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); + } }; - 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; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); } - if ( c0 === 0x2C /* , */ ) { - if ( needIdentifier ) { return; } - i += 1; - needIdentifier = true; - continue; + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; } - 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; + 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; } - 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; + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); } - if ( this.v2 ) { return; } - const r = this.#consumeUnquotedIdentifier(query, i); - if ( r === undefined ) { return; } - keys.push(r.s); - i = r.i; + }; + 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 ( 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 }; + 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)); } - const match = this.#reUnquotedIdentifier.exec(query.slice(i)); - if ( match === null ) { return; } - return { s: match[0], i: i + match[0].length }; + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; } - #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; + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +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); } - 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; + 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; } - 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()}`); + return modified; } - 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; + 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; } - 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; + return found; } - 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 ( prop === '{-}' && owner instanceof Object ) { + for ( const key of Object.keys(owner) ) { + if ( objectFindOwnerFn(owner[key], next) === false ) { continue; } + delete owner[key]; + found = true; } - if ( this.#compiled.re === null ) { return; } - obj[key] = lval.replace(this.#compiled.re, rval.replacement); - break; + return found; } - default: - break; + 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); } } function safeSelf() { @@ -21382,149 +34467,139 @@ function safeSelf() { } return safe; } -function collateFetchArgumentsFn(resource, options) { +function getRandomTokenFn() { 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]; - } - }; + 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 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 ( - typeof resource !== 'object' || - safe.Object_toString.call(resource) !== '[object Request]' + logLevel === 'all' || + logLevel === 'match' && r || + logLevel === 'nomatch' && !r ) { - 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); + safe.uboLog(stack.replace(/\t/g, '\n')); } - return out; + return r; } -function jsonEditFetchRequestFn(trusted, jsonq = '', ...varargs) { +function objectPruneFn( + obj, + rawPrunePaths, + rawNeedlePaths, + stackNeedleDetails = { matchAll: true }, + extraArgs = {} +) { + if ( typeof rawPrunePaths !== 'string' ) { return; } 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 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; + } } - 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); + if ( objectPruneFn.mustProcess === undefined ) { + objectPruneFn.mustProcess = (root, needlePaths) => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; + } + } + return true; + }; } - 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')}`); + 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'; } } - 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); + } + if ( outcome === 'match' ) { return obj; } } -function trustedJsonEditFetchRequest(jsonq = '', ...args) { - jsonEditFetchRequestFn(true, jsonq, ...args); +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; + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonEditFetchRequest(...args); -}, -}; - - -scriptlets['jsonl-edit-xhr-response.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...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}`); - } + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - return matched; +} catch { } +evaldataPrune(...args); +}, +}; + + +scriptlets['noeval-if.js'] = { +aliases: ["prevent-eval-if.js"], + +requiresTrust: false, +fn: 'noEvalIf', +dependencies: ["safeSelf","proxyApplyFn"], +func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -21699,651 +34774,536 @@ function safeSelf() { } return safe; } -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); -} -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; +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); } - 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); + const fn = context[prop]; + if ( typeof fn !== 'function' ) { return; } + if ( proxyApplyFn.CtorContext === undefined ) { + proxyApplyFn.ctorContexts = []; + proxyApplyFn.CtorContext = class { + constructor(...args) { + this.init(...args); } - try { r.rval = JSON.parse(val); } - catch { return; } + 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; } - 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; + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); } - 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 proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); } - const result = this.#root['$'] ?? null; - this.#root = null; - return result; - } - dump() { - return JSON.stringify(this.#compiled); + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; } - toJSON(obj, ...args) { - return JSONPath.toJSON(obj, null, ...args) + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +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(); + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - get [Symbol.toStringTag]() { - return 'JSONPath'; +} catch { +} +noEvalIf(...args); +}, +}; + + +scriptlets['prevent-addEventListener.js'] = { +aliases: ["addEventListener-defuser.js","aeld.js"], + +requiresTrust: false, +fn: 'preventAddEventListener', +dependencies: ["safeSelf","runAt","proxyApplyFn"], +func: function (scriptletGlobals = {}, ...args) { +function safeSelf() { + if ( safeSelf.safe ) { + return safeSelf.safe; } - #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; + 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 }; } - // 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; + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); } - 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; + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; } - 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 ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; } - 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; + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test(details.re, haystack) === details.expect; } - // 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; + 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); } - if ( query.startsWith('[*]', i) ) { - mv ||= this.#CHILDREN; - steps.push({ mv, k: '*' }); - i += 3; - mv = this.#UNDEFINED; - continue; + try { + return new RegExp(match[1], match[2] || undefined); } - 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; + 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 }); } - case this.#QUANTIFIER: { - const { length } = resultset; - if ( length < step.min || length > step.max ) { return []; } - resultset = []; + 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; - } - default: + 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; +} +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 resultset; + return 0; + }; + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; } - #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 ]); - } + 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 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); } - 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 ]); - } + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; } - } - 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); + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; } - 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); + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); } - 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; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); } - 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; + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; } - 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; + 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; } - 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; + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); } - 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; + }; + 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); } - } - 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 {}; } + }); + proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); + Function.prototype.toString = proxiedToString; } - 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; + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); } - #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; + 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 jsonlEditXhrResponseFn(trusted, jsonq = '', ...varargs) { +function preventAddEventListener( + type = '', + pattern = '', + ...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); + 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 } - 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'); + 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]); } - return (xhrDetails.response = outerResponse); + } catch { } - get responseText() { - const response = this.response; - return typeof response !== 'string' - ? super.responseText - : response; + 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(); }; -} -function jsonlEditXhrResponse(jsonq = '', ...args) { - jsonlEditXhrResponseFn(false, jsonq, ...args); + 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); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -22353,55 +35313,18 @@ try { } } catch { } -jsonlEditXhrResponse(...args); +preventAddEventListener(...args); }, }; -scriptlets['trusted-jsonl-edit-xhr-response.js'] = { -aliases: [], +scriptlets['prevent-bab.js'] = { +aliases: ["bab-defuser.js","nobab.js"], -requiresTrust: true, +requiresTrust: false, +fn: 'preventBab', +dependencies: ["safeSelf","proxyApplyFn"], func: function (scriptletGlobals = {}, ...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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -22570,676 +35493,395 @@ function safeSelf() { } catch { safe.sendToLogger = (type, ...args) => { const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; -} -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); -} -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 ( text === undefined ) { return; } + safe.log(`uBO ${text}`); }; - 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; + return safe; +} +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); } - if ( c0 === 0x2C /* , */ ) { - if ( needIdentifier ) { return; } - i += 1; - needIdentifier = true; - continue; + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; } - 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; + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; } - 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; + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); } - 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; + }; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); } - 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; - } + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; } - 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; } + 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; } - 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] }; + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); } - #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; + const proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); } - return hasOwn === target; + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; } - #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; + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +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; } } - break; + if ( (match / tokens.length) >= 0.8 ) { return true; } } - 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; + return false; + }; + proxyApplyFn('eval', function(context) { + const a = context.callArgs[0]; + if ( !check(a) ) { + return context.reflect(); } - 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; + safe.uboLog(logPrefix, 'Prevented'); + if ( document.body ) { + document.body.style.removeProperty('visibility'); } - default: - break; + 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(); + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } +} catch { } -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'); +preventBab(...args); +}, +}; + + +scriptlets['prevent-clipboard-write.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'preventClipboardWrite', +dependencies: ["safeSelf","runAt","proxyApplyFn"], +func: function (scriptletGlobals = {}, ...args) { +function safeSelf() { + if ( safeSelf.safe ) { + return safeSelf.safe; } - 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); + 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 }; } - return super.open(method, url, ...args); - } - get response() { - const innerResponse = super.response; - const xhrDetails = xhrInstances.get(this); - if ( xhrDetails === undefined ) { - return innerResponse; + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); } - const responseLength = typeof innerResponse === 'string' - ? innerResponse.length - : undefined; - if ( xhrDetails.lastResponseLength !== responseLength ) { - xhrDetails.response = undefined; - xhrDetails.lastResponseLength = responseLength; + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; } - if ( xhrDetails.response !== undefined ) { - return xhrDetails.response; + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; } - if ( typeof innerResponse !== 'string' ) { - return (xhrDetails.response = innerResponse); + 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 }); } - const outerResponse = jsonlEditFn(jsonp, innerResponse, jsonExtract); - if ( outerResponse !== innerResponse ) { - safe.uboLog(logPrefix, 'Pruned'); + 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; } - return (xhrDetails.response = outerResponse); - } - get responseText() { - const response = this.response; - return typeof response !== 'string' - ? super.responseText - : response; + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +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; }; -} -function trustedJsonlEditXhrResponse(jsonq = '', ...args) { - jsonlEditXhrResponseFn(true, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; } -} catch { + 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); } -trustedJsonlEditXhrResponse(...args); -}, -}; - - -scriptlets['jsonl-edit-fetch-response.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', handler = '', @@ -23338,625 +35980,541 @@ function proxyApplyFn( proxyApplyFn.proxies.set(proxiedTarget, fn); context[prop] = proxiedTarget; } -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) { +function preventClipboardWrite(matches = '', ...varargs) { 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; } + 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'); } - if ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); - } - } - return matched; -} -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; + code.style = styles.join(';'); + code.textContent = clipboardText; + html = htmlTemplate.replace('${warning}', + domAlert.replace('${text}', code.outerHTML) + ); + } else { + html = htmlTemplate.replace('${warning}', domAlert); } - const jsonBefore = match[1]; - let obj; - try { obj = safe.JSON_parse(jsonBefore); } catch { } - if ( typeof obj !== 'object' || obj === null ) { - linesAfter.push(lineBefore); - continue; + 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(); } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { - linesAfter.push(lineBefore); - continue; + }; + 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; } } - 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); -} -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; } + if ( extraArgs.domAlert ) { + domAlert(text); } - 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]; + 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; } } - } - 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) + return context.reflect(); + }, { skipToString: true }); + }; + runAt(( ) => { + self.document.addEventListener('mousemove', installTraps, { + once: true, + capture: true, + }); + }, 'interactive') +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - get [Symbol.toStringTag]() { - return 'JSONPath'; +} catch { +} +preventClipboardWrite(...args); +}, +}; + + +scriptlets['prevent-dialog.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'preventDialog', +dependencies: ["safeSelf"], +func: function (scriptletGlobals = {}, ...args) { +function safeSelf() { + if ( safeSelf.safe ) { + return safeSelf.safe; } - #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; + 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 }; } - // 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; + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); } - 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; + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; } - 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 ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; } - 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; + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test(details.re, haystack) === details.expect; } - // 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; + 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); } - if ( query.startsWith('[*]', i) ) { - mv ||= this.#CHILDREN; - steps.push({ mv, k: '*' }); - i += 3; - mv = this.#UNDEFINED; - continue; + try { + return new RegExp(match[1], match[2] || undefined); } - 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; + 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; } } - 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; + 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 }); } - case this.#QUANTIFIER: { - const { length } = resultset; - if ( length < step.min || length > step.max ) { return []; } - resultset = []; + 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; - } - default: + 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; +} +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'); } - return resultset; + }; + let debouncer; + const observer = new MutationObserver(( ) => { + if ( debouncer !== undefined ) { return; } + debouncer = requestAnimationFrame(prevent); + }); + observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - #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 ]); - } +} catch { +} +preventDialog(...args); +}, +}; + + +scriptlets['prevent-fetch.js'] = { +aliases: ["no-fetch-if.js"], + +requiresTrust: false, +fn: 'preventFetch', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","generateContentFn","safeSelf","collateFetchArgumentsFn","preventFetchFn"], +func: function (scriptletGlobals = {}, ...args) { +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); } - 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 ]); - } + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; } - } - 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); + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; } - 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); + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); } - 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; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); } - 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; + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; } - 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; + 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; } - 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; + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); } - 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; + }; + 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); } - } - end += 1; + }); + proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); + Function.prototype.toString = proxiedToString; } - 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; + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); } - #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 {}; } + const proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); } - return { obj, key, value: obj[key] }; + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; } - #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; + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +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 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; + 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; } } - break; + if ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); } - 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; + } + 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; } - 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 { } + 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); } - if ( this.#compiled.re === null ) { return; } - obj[key] = lval.replace(this.#compiled.re, rval.replacement); - break; - } - default: - break; - } + 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 safeSelf() { if ( safeSelf.safe ) { @@ -24166,68 +36724,89 @@ function collateFetchArgumentsFn(resource, options) { } return out; } -function jsonlEditFetchResponseFn(trusted, jsonq = '', ...varargs) { +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( - `${trusted ? 'trusted-' : ''}jsonl-edit-fetch-response`, - jsonq + scriptletName, + propsToMatch, + responseBody, + responseType ); - 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 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; } - 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; + if ( validResponseProps[p] === undefined ) { return; } + if ( validResponseProps[p].includes(v) === false ) { return; } + responseProps[p] = { value: v }; }); - }).catch(reason => { - safe.uboErr(logPrefix, 'Error:', reason); - return fetchPromise; + } + 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; }); }); } -function jsonlEditFetchResponse(jsonq = '', ...args) { - jsonlEditFetchResponseFn(false, jsonq, ...args); +function preventFetch(...args) { + preventFetchFn(false, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -24237,15 +36816,17 @@ try { } } catch { } -jsonlEditFetchResponse(...args); +preventFetch(...args); }, }; -scriptlets['trusted-jsonl-edit-fetch-response.js'] = { +scriptlets['trusted-prevent-fetch.js'] = { aliases: [], requiresTrust: true, +fn: 'trustedPreventFetch', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","generateContentFn","safeSelf","collateFetchArgumentsFn","preventFetchFn"], func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', @@ -24363,608 +36944,902 @@ function parsePropertiesToMatchFn(propsToMatch, implicit = '') { needles.set(implicit, safe.initPattern(prop, options)); } } - return needles; + 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 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; } -function matchObjectPropertiesFn(propNeedles, ...objs) { +function collateFetchArgumentsFn(resource, options) { 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; } + 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 ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); + }; + } + 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); } - return matched; + if ( typeof options === 'object' && options !== null ) { + collateFetchArgumentsFn.collateKnownProps(options, out); + } + return out; } -function jsonlEditFn(jsonp, text = '', jsonExtract) { +function preventFetchFn( + trusted = false, + propsToMatch = '', + responseBody = '', + responseType = '', + ...varargs +) { 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 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 }; + }); } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { - linesAfter.push(lineBefore); - continue; + catch { } + } else if ( responseType !== '' ) { + if ( validResponseProps.type.includes(responseType) ) { + responseProps.type = { value: responseType }; } - 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); -} -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; } + 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')}`); } - 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]; - } + if ( propsToMatch === '' && responseBody === '' ) { + return context.reflect(); } - 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) + 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; + }); + }); +} +function trustedPreventFetch(...args) { + preventFetchFn(true, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - get [Symbol.toStringTag]() { - return 'JSONPath'; +} catch { +} +trustedPreventFetch(...args); +}, +}; + + +scriptlets['freeze-element-property.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'freezeElementProperty', +dependencies: ["safeSelf"], +func: function (scriptletGlobals = {}, ...args) { +function safeSelf() { + if ( safeSelf.safe ) { + return safeSelf.safe; } - #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; + 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 }; } - // 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; + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); } - 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; + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; } - 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 ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; } - 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; + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test(details.re, haystack) === details.expect; } - // 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; + 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); } - if ( query.startsWith('[*]', i) ) { - mv ||= this.#CHILDREN; - steps.push({ mv, k: '*' }); - i += 3; - mv = this.#UNDEFINED; - continue; + try { + return new RegExp(match[1], match[2] || undefined); } - 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; + 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 }); } - case this.#QUANTIFIER: { - const { length } = resultset; - if ( length < step.min || length > step.max ) { return []; } - resultset = []; + 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; - } - default: + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; 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; + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; } - #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; + return safe; +} +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 ( typeof k === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - return [ k >= 0 ? k : owner.length + k ]; + if ( Object.hasOwn(HTMLElement.prototype, property) ) { + return HTMLElement.prototype; } - if ( k === '*' ) { - if ( Array.isArray(owner) ) { return owner.keys(); } - return JSONPath.keys(owner); + if ( Object.hasOwn(Element.prototype, property) ) { + return Element.prototype; } - 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; + if ( Object.hasOwn(Node.prototype, property) ) { + return Node.prototype; } - 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; } + 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; + }, + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - #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; +} catch { +} +freezeElementProperty(...args); +}, +}; + + +scriptlets['prevent-innerHTML.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'preventInnerHTML', +dependencies: ["safeSelf","freezeElementProperty"], +func: function (scriptletGlobals = {}, ...args) { +function safeSelf() { + if ( safeSelf.safe ) { + return safeSelf.safe; } - #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; + 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 }; } - if ( c0 === 0x2C /* , */ ) { - if ( needIdentifier ) { return; } - i += 1; - needIdentifier = true; - continue; + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); } - 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; + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; } - 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 ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; } - 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; + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test(details.re, haystack) === details.expect; } - 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; - } + 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); } - 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 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; } } - return hasOwn === target; + 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}`); + }; } - #modifyVal(obj, key) { - let { modify, rval } = this.#compiled; - if ( typeof rval === 'string' ) { - rval = rval.replace('${now}', `${Date.now()}`); + return safe; +} +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; } - 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; + if ( Object.hasOwn(HTMLElement.prototype, property) ) { + return HTMLElement.prototype; } - 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; + if ( Object.hasOwn(Element.prototype, property) ) { + return Element.prototype; } - 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; + if ( Object.hasOwn(Node.prototype, property) ) { + return Node.prototype; } - default: - break; + 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; + }, + }); +} +function preventInnerHTML( + selector = '', + pattern = '' +) { + freezeElementProperty('innerHTML', selector, pattern); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } +} catch { } +preventInnerHTML(...args); +}, +}; + + +scriptlets['prevent-navigation.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'preventNavigation', +dependencies: ["safeSelf"], +func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -25139,102 +38014,25 @@ function safeSelf() { } return safe; } -function collateFetchArgumentsFn(resource, options) { +function preventNavigation( + pattern = '' +) { 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 { + 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; } - collateFetchArgumentsFn.collateKnownProps(clone || resource, out); - } - if ( typeof options === 'object' && options !== null ) { - collateFetchArgumentsFn.collateKnownProps(options, out); - } - return out; -} -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')}`); - } + if ( safe.testPattern(matcher, url) ) { + ev.preventDefault(); + safe.uboLog(logPrefix, `Prevented navigation to ${url}`); } - 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; - }); }); -} -function trustedJsonlEditFetchResponse(jsonq = '', ...args) { - jsonlEditFetchResponseFn(true, jsonq, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -25244,60 +38042,18 @@ try { } } catch { } -trustedJsonlEditFetchResponse(...args); +preventNavigation(...args); }, }; -scriptlets['abort-on-stack-trace.js'] = { -aliases: ["aost.js"], +scriptlets['prevent-setTimeout.js'] = { +aliases: ["no-setTimeout-if.js","nostif.js","setTimeout-defuser.js"], requiresTrust: false, +fn: 'preventSetTimeout', +dependencies: ["safeSelf","RangeParser","proxyApplyFn"], func: function (scriptletGlobals = {}, ...args) { -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; -} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -25472,195 +38228,180 @@ function safeSelf() { } 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 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; +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; } - const prop = chain.slice(0, pos); - let v = owner[prop]; - chain = chain.slice(pos + 1); - if ( v ) { - makeProxy(v, chain); - return; + if ( pos !== -1 ) { + this.max = parseInt(s.slice(pos + 1), 10) || Number.MAX_SAFE_INTEGER; } - 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); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); } -} catch { -} -abortOnStackTrace(...args); -}, -}; - - -scriptlets['trusted-prune-inbound-object.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -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); + 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; } - }.bind(); - return token; + 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; + } } -function matchesStackTraceFn( - needleDetails, - logLevel = '' +function proxyApplyFn( + target = '', + handler = '', + options = {} ) { - 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()); + 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); } - 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')); + 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; + } } - return r; + 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 objectPruneFn( - obj, - rawPrunePaths, - rawNeedlePaths, - stackNeedleDetails = { matchAll: true }, - extraArgs = {} +function preventSetTimeout( + needleRaw = '', + delayRaw = '' ) { - 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; + 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 ( 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 ( reNeedle.test(a) !== needleNot && range.test(b) ) { + callArgs[0] = function(){}; + safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`); } + return context.reflect(); + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - if ( outcome === 'match' ) { return obj; } +} catch { } +preventSetTimeout(...args); +}, +}; + + +scriptlets['prevent-setInterval.js'] = { +aliases: ["no-setInterval-if.js","nosiif.js","setInterval-defuser.js"], + +requiresTrust: false, +fn: 'preventSetInterval', +dependencies: ["safeSelf","RangeParser","proxyApplyFn"], +func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -25835,156 +38576,36 @@ function safeSelf() { } return safe; } -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; +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 ( - 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 ( pos !== -1 ) { + this.max = parseInt(s.slice(pos + 1), 10) || Number.MAX_SAFE_INTEGER; } - if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } - owner = owner[prop]; - chain = chain.slice(pos + 1); - } -} -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, / +/)); + unbound() { + return this.min === undefined && this.max === undefined; } - const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); - const mustProcess = root => { - for ( const needlePath of needlePaths ) { - if ( objectFindOwnerFn(root, needlePath) === false ) { - return false; - } + 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; } - 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); - }, - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + 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; } -} catch { } -trustedPruneInboundObject(...args); -}, -}; - - -scriptlets['trusted-prune-outbound-object.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', handler = '', @@ -26083,70 +38704,52 @@ function proxyApplyFn( proxyApplyFn.proxies.set(proxiedTarget, fn); context[prop] = proxiedTarget; } -function objectFindOwnerFn( - root, - path, - prune = false +function preventSetInterval( + needleRaw = '', + delayRaw = '' ) { 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; + 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 ( - 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 ( reNeedle.test(a) !== needleNot && range.test(b) ) { + callArgs[0] = function(){}; + safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`); } - if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } - owner = owner[prop]; - chain = chain.slice(pos + 1); + return context.reflect(); + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } +} catch { } +preventSetInterval(...args); +}, +}; + + +scriptlets['prevent-requestAnimationFrame.js'] = { +aliases: ["no-requestAnimationFrame-if.js","norafif.js"], + +requiresTrust: false, +fn: 'preventRequestAnimationFrame', +dependencies: ["safeSelf","proxyApplyFn"], +func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -26292,176 +38895,35 @@ function safeSelf() { 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; -} -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 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; -} -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 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + }; + 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}`); + }; } -} catch { + return safe; } -trustedPruneOutboundObject(...args); -}, -}; - - -scriptlets['json-prune.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', handler = '', @@ -26560,69 +39022,97 @@ function proxyApplyFn( proxyApplyFn.proxies.set(proxiedTarget, fn); context[prop] = proxiedTarget; } -function objectFindOwnerFn( - root, - path, - prune = false +function preventRequestAnimationFrame( + needleRaw = '' ) { 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; + 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}`); } - 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; + return context.reflect(); + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +preventRequestAnimationFrame(...args); +}, +}; + + +scriptlets['set-constant.js'] = { +aliases: ["set.js"], + +requiresTrust: false, +fn: 'setConstant', +dependencies: ["validateConstantFn","safeSelf","runAt","setConstantFn"], +func: function (scriptletGlobals = {}, ...args) { +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; } } - 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; + } 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); } - if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } - owner = owner[prop]; - chain = chain.slice(pos + 1); } + return value; } function safeSelf() { if ( safeSelf.safe ) { @@ -26798,358 +39288,259 @@ function safeSelf() { } 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 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; -} -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; +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, }; - } - 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'; - } + 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; } - if ( outcome === 'match' ) { return obj; } + 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 jsonPrune( - rawPrunePaths = '', - rawNeedlePaths = '', - stackNeedle = '', +function setConstantFn( + trusted = false, + chain = '', + rawValue = '', ...varargs ) { + if ( chain === '' ) { return; } const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('json-prune', rawPrunePaths, rawNeedlePaths, stackNeedle); - const stackNeedleDetails = safe.initPattern(stackNeedle, { canNegate: true }); + const logPrefix = safe.makeLogPrefix('set-constant', chain, rawValue); 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonPrune(...args); -}, -}; - - -scriptlets['json-prune-fetch-response.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...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)); + 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); } - } - return needles; -} -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 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}`); } - let modified = false; - if ( chain === '*' ) { - for ( const key in owner ) { - if ( safe.Object_hasOwn(owner, key) === false ) { continue; } - delete owner[key]; - modified = true; + 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; } - } 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; + 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); } - 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; + }; + 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; } - 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; + 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; } - return found; - } - if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } - owner = owner[prop]; - chain = chain.slice(pos + 1); + 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); } -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 matchesStackTraceFn( - needleDetails, - logLevel = '' +function setConstant( + ...args ) { - 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')); + setConstantFn(false, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - return r; +} catch { } -function objectPruneFn( - obj, - rawPrunePaths, - rawNeedlePaths, - stackNeedleDetails = { matchAll: true }, - extraArgs = {} -) { - if ( typeof rawPrunePaths !== 'string' ) { return; } +setConstant(...args); +}, +}; + + +scriptlets['trusted-set-constant.js'] = { +aliases: ["trusted-set.js"], + +requiresTrust: true, +fn: 'trustedSetConstant', +dependencies: ["validateConstantFn","safeSelf","runAt","setConstantFn"], +func: function (scriptletGlobals = {}, ...args) { +function validateConstantFn(trusted, raw, extraArgs = {}) { 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'; - } + 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 ( outcome === 'match' ) { return obj; } -} -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}`); + 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 matched; + return value; } function safeSelf() { if ( safeSelf.safe ) { @@ -27325,305 +39716,259 @@ function safeSelf() { } return safe; } -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]; - } +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, }; - } - 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 { + 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]; } - collateFetchArgumentsFn.collateKnownProps(clone || resource, out); - } - if ( typeof options === 'object' && options !== null ) { - collateFetchArgumentsFn.collateKnownProps(options, out); + return 0; + }; + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; } - return out; + 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 jsonPruneFetchResponse( - rawPrunePaths = '', - rawNeedlePaths = '', +function setConstantFn( + trusted = false, + chain = '', + rawValue = '', ...varargs ) { + if ( chain === '' ) { return; } const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('json-prune-fetch-response', rawPrunePaths, rawNeedlePaths); + const logPrefix = safe.makeLogPrefix('set-constant', chain, rawValue); 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; + 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); + }, }); - }).catch(reason => { - safe.uboErr(logPrefix, 'Error:', reason); - return fetchPromise; - }); - }; - self.fetch = new Proxy(self.fetch, { - apply: applyHandler - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonPruneFetchResponse(...args); -}, -}; - - -scriptlets['json-prune-xhr-response.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...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)); + }; + if ( trappedProp === '' ) { return; } + const thisScript = document.currentScript; + let normalValue = validateConstantFn(trusted, rawValue, extraArgs); + if ( rawValue === 'noopFunc' || rawValue === 'trueFunc' || rawValue === 'falseFunc' ) { + normalValue = cloakFunc(normalValue); } - } - return needles; -} -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 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}`); } - let modified = false; - if ( chain === '*' ) { - for ( const key in owner ) { - if ( safe.Object_hasOwn(owner, key) === false ) { continue; } - delete owner[key]; - modified = true; + 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; } - } 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; + 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); } - 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; + }; + 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; } - 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; + 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; } - return found; - } - if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } - owner = owner[prop]; - chain = chain.slice(pos + 1); + 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); } -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 matchesStackTraceFn( - needleDetails, - logLevel = '' +function trustedSetConstant( + ...args ) { - 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')); + setConstantFn(true, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - return r; +} catch { } -function objectPruneFn( - obj, - rawPrunePaths, - rawNeedlePaths, - stackNeedleDetails = { matchAll: true }, - extraArgs = {} -) { - if ( typeof rawPrunePaths !== 'string' ) { return; } +trustedSetConstant(...args); +}, +}; + + +scriptlets['trusted-replace-argument.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedReplaceArgument', +dependencies: ["validateConstantFn","safeSelf","proxyApplyFn","ArglistParser","parseReplaceFn"], +func: function (scriptletGlobals = {}, ...args) { +function validateConstantFn(trusted, raw, extraArgs = {}) { 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; + 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 ( 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 ( 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); } } - if ( outcome === 'match' ) { return obj; } + return value; } function safeSelf() { if ( safeSelf.safe ) { @@ -27799,106 +40144,281 @@ function safeSelf() { } return safe; } -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; } +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); } - if ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); + 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; } } - return matched; + 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 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}"`); +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; } - 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; + 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); } - let objBefore; - if ( typeof innerResponse === 'object' ) { - objBefore = innerResponse; - } else if ( typeof innerResponse === 'string' ) { - try { - objBefore = safe.JSON_parse(innerResponse); - } catch { - } + if ( this.reOddTrailingEscape.test(pattern.slice(0, pos)) === false ) { + return (this.argEnd = pos); } - if ( typeof objBefore !== 'object' ) { - return (xhrDetails.response = innerResponse); + 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 = '', + 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; } - 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; + } 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(); } - return (xhrDetails.response = outerResponse); } - get responseText() { - const response = this.response; - return typeof response !== 'string' - ? super.responseText - : response; + const argAfter = replacer(argBefore); + if ( argAfter !== argBefore ) { + setArg(context, argAfter); + safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`); } - }; + return context.reflect(); + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -27908,178 +40428,18 @@ try { } } catch { } -jsonPruneXhrResponse(...args); +trustedReplaceArgument(...args); }, }; -scriptlets['evaldata-prune.js'] = { +scriptlets['spoof-css.js'] = { aliases: [], requiresTrust: false, +fn: 'spoofCSS', +dependencies: ["safeSelf"], func: function (scriptletGlobals = {}, ...args) { -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 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); - } -} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -28254,137 +40614,280 @@ function safeSelf() { } return safe; } -function getRandomTokenFn() { +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(); - 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); + 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}`); } - }.bind(); - return token; + 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); + }, + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { } -function matchesStackTraceFn( - needleDetails, - logLevel = '' +spoofCSS(...args); +}, +}; + + +scriptlets['prevent-xhr.js'] = { +aliases: ["no-xhr-if.js"], + +requiresTrust: false, +fn: 'preventXhr', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","generateContentFn","preventXhrFn"], +func: function (scriptletGlobals = {}, ...args) { +function proxyApplyFn( + target = '', + handler = '', + options = {} ) { - 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 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; } - 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')); + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); } - return r; + 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 objectPruneFn( - obj, - rawPrunePaths, - rawNeedlePaths, - stackNeedleDetails = { matchAll: true }, - extraArgs = {} -) { - if ( typeof rawPrunePaths !== 'string' ) { return; } +function parsePropertiesToMatchFn(propsToMatch, implicit = '') { 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; + 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 ( 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 ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); } } - if ( outcome === 'match' ) { return obj; } + return needles; } -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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); +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}`); + } } -} catch { + return matched; } -evaldataPrune(...args); -}, -}; - - -scriptlets['noeval-if.js'] = { -aliases: ["prevent-eval-if.js"], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -28559,6 +41062,261 @@ function safeSelf() { } return safe; } +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 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'); + }); +} +function preventXhr(...args) { + preventXhrFn(false, ...args); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +preventXhr(...args); +}, +}; + + +scriptlets['trusted-prevent-xhr.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'trustedPreventXhr', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","generateContentFn","preventXhrFn"], +func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', handler = '', @@ -28657,44 +41415,45 @@ function proxyApplyFn( proxyApplyFn.proxies.set(proxiedTarget, fn); context[prop] = proxiedTarget; } -function noEvalIf( - needle = '' -) { - if ( typeof needle !== 'string' ) { return; } +function parsePropertiesToMatchFn(propsToMatch, implicit = '') { 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; + 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 ( needle === '' || safe.logLevel > 1 ) { - safe.uboLog(logPrefix, 'Not prevented:\n', a); + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); } - return context.reflect(); - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); } -} catch { + 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; } -noEvalIf(...args); -}, -}; - - -scriptlets['prevent-addEventListener.js'] = { -aliases: ["addEventListener-defuser.js","aeld.js"], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -28857,236 +41616,252 @@ function safeSelf() { 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; -} -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 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); + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; } - const proxyDetails = { - apply(target, thisArg, args) { - return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); + return safe; +} +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 ( proxyApplyFn.isCtor.get(target) ) { - proxyDetails.construct = function(target, args) { - return handler(proxyApplyFn.CtorContext.factory(target, args)); - }; + if ( directive === 'true' ) { + return randomize(10); } - const proxiedTarget = new Proxy(fn, proxyDetails); - proxyApplyFn.proxies.set(proxiedTarget, fn); - context[prop] = proxiedTarget; + 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 preventAddEventListener( - type = '', - pattern = '', - ...varargs +function preventXhrFn( + trusted = false, + propsToMatch = '', + directive = '' ) { + if ( typeof propsToMatch !== 'string' ) { return; } 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 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 { + } }; - 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))}`); + 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(); } - 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}"]`); + const haystack = { method, url }; + if ( propsToMatch === '' && directive === '' ) { + safe.uboLog(logPrefix, `Called: ${safe.JSON_stringify(haystack, null, 2)}`); + return context.reflect(); } - 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 ( 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); } - if ( matchesBoth && targetSelector !== undefined ) { - if ( elementMatches(thisArg) === false ) { return false; } + return context.reflect(); + }); + proxyApplyFn('XMLHttpRequest.prototype.send', function(context) { + const { thisArg } = context; + const xhrDetails = xhrInstances.get(thisArg); + if ( xhrDetails === undefined ) { + return context.reflect(); } - 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)); - } + 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 { - h = String(callArgs[1]); + xhrDetails.props.response.value = xhrText; + xhrDetails.props.responseText.value = xhrText; } - } catch { + xhrDetails.headers['content-type'] = 'text/plain'; + break; } - 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; } + 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}`); }); - }; - 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); } + }); + 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(); } - if ( Object.hasOwn(window, 'addEventListener') ) { - proxyApplyFn('window.addEventListener', proxyFn); - if ( extraArgs.protect ) { protect(window); } + 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(); } - }, extraArgs.runAt); + 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 trustedPreventXhr(...args) { + preventXhrFn(true, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -29096,16 +41871,58 @@ try { } } catch { } -preventAddEventListener(...args); +trustedPreventXhr(...args); }, }; -scriptlets['prevent-bab.js'] = { -aliases: ["bab-defuser.js","nobab.js"], +scriptlets['mpegdash-prune.js'] = { +aliases: [], requiresTrust: false, +fn: 'mpegdashPrune', +dependencies: ["proxyToStringFn","parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","modifyXhrResponseFn"], func: function (scriptletGlobals = {}, ...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 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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -29280,165 +42097,209 @@ function safeSelf() { } return safe; } -function proxyApplyFn( - target = '', - handler = '', - options = {} +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 modifyXhrResponseFn( + propsToMatch = '', + modifierFn = '' ) { - 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); + if ( typeof propsToMatch !== 'string' ) { return; } + const safe = safeSelf(); + if ( modifyXhrResponseFn.xhrInstances === undefined ) { + modifyXhrResponseFn.xhrInstances = new WeakMap(); } - 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); + 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); } - }; - 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; + 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 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); } - } - 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)); + 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; }; - 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 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; } + 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); } - if ( (match / tokens.length) >= 0.8 ) { return true; } + const mpds = queryAll(xmlDoc, 'MPD[mediaPresentationDuration]'); + if ( mpds.length !== 1 ) { return; } + mpds[0].setAttribute('mediaPresentationDuration', ptFromSeconds(seconds)); + } catch { } - return false; }; - proxyApplyFn('eval', function(context) { - const a = context.callArgs[0]; - if ( !check(a) ) { - return context.reflect(); + 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}`); } - safe.uboLog(logPrefix, 'Prevented'); - if ( document.body ) { - document.body.style.removeProperty('visibility'); + return xmlDoc; + }; + const pruneFromText = text => { + if ( (/^\s*\s*$/.test(text)) === false ) { + return text; } - const el = document.getElementById('babasbmsgx'); - if ( el ) { - el.parentNode.removeChild(el); + try { + const xmlParser = new DOMParser(); + const xmlDoc = xmlParser.parseFromString(text, 'text/xml'); + pruneFromDoc(xmlDoc); + const serializer = new XMLSerializer(); + text = serializer.serializeToString(xmlDoc); + } catch { } - }); - 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 text; + }; + modifyXhrResponseFn(propsToMatch, (xhr, before) => { + if ( before instanceof XMLDocument ) { + return pruneFromDoc(before); } - return context.reflect(); + if ( typeof before === 'string' ) { + return pruneFromText(before); + } + return before; }); }; try { @@ -29449,16 +42310,95 @@ try { } } catch { } -preventBab(...args); +mpegdashPrune(...args); }, }; -scriptlets['prevent-clipboard-write.js'] = { +scriptlets['set-cookie.js'] = { aliases: [], - -requiresTrust: true, +world: 'ISOLATED', +requiresTrust: false, +fn: 'setCookie', +dependencies: ["getCookieFn","setCookieFn","safeSelf","getSafeCookieValuesFn"], func: function (scriptletGlobals = {}, ...args) { +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(); + } +} +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; +} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -29600,258 +42540,98 @@ function safeSelf() { 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; -} -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 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 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; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); } - return context.reflect(); - }, { skipToString: true }); - }; - runAt(( ) => { - self.document.addEventListener('mousemove', installTraps, { - once: true, - capture: true, - }); - }, 'interactive') + 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; +} +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 = '', + 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'); + } }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -29861,16 +42641,95 @@ try { } } catch { } -preventClipboardWrite(...args); +setCookie(...args); }, }; -scriptlets['prevent-dialog.js'] = { +scriptlets['set-cookie-reload.js'] = { aliases: [], world: 'ISOLATED', requiresTrust: false, +fn: 'setCookieReload', +dependencies: ["getCookieFn","setCookieFn","safeSelf","getSafeCookieValuesFn","setCookie"], func: function (scriptletGlobals = {}, ...args) { +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(); + } +} +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; +} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -30045,31 +42904,68 @@ function safeSelf() { } return safe; } -function preventDialog( - selector = '', +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 = '', + path = '', + ...varargs ) { + if ( name === '' ) { return; } 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, - }); + 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'); + } +} +function setCookieReload(name, value, path, ...args) { + setCookie(name, value, path, 'reload', '1', ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -30079,217 +42975,94 @@ try { } } catch { } -preventDialog(...args); +setCookieReload(...args); }, }; -scriptlets['prevent-fetch.js'] = { -aliases: ["no-fetch-if.js"], - -requiresTrust: false, +scriptlets['trusted-set-cookie.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedSetCookie', +dependencies: ["getCookieFn","setCookieFn","safeSelf"], func: function (scriptletGlobals = {}, ...args) { -function proxyApplyFn( - target = '', - handler = '', - options = {} +function getCookieFn( + name = '' ) { - 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 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}`); - } + 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(); } - 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 '{}'; +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); } - if ( directive === 'emptyArr' ) { - return '[]'; + // 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); } - if ( directive === 'emptyStr' ) { - return ''; + + 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 ( 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 ( path === '' ) { path = '/'; } + else if ( path === 'none' ) { path = ''; } + if ( path !== '' && path !== '/' ) { return; } + if ( path === '/' ) { + cookieParts.push('; path=/'); } - 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); + + 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; } - 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 ( domain ) { + cookieParts.push(`; domain=${domain}`); + } + } + cookieParts.push('; Secure'); + } else if ( /^__(Host|Secure)-/.test(name) ) { + cookieParts.push('; Secure'); } - if ( trusted ) { - return directive; + + try { + document.cookie = cookieParts.join(''); + } catch { } - return ''; + + const done = getCookieFn(name) === value; + if ( done && options.reload ) { + window.location.reload(); + } + + return done; } function safeSelf() { if ( safeSelf.safe ) { @@ -30465,123 +43238,54 @@ function safeSelf() { } return safe; } -function collateFetchArgumentsFn(resource, options) { +function trustedSetCookie( + name = '', + value = '', + offsetExpiresSec = '', + path = '', + ...varargs +) { + if ( name === '' ) { return; } + 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]; - } - }; + const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path); + const time = new Date(); + + if ( value.includes('$now$') ) { + value = value.replaceAll('$now$', time.getTime()); } - 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 ( value.includes('$currentDate$') ) { + value = value.replaceAll('$currentDate$', time.toUTCString()); } - if ( typeof options === 'object' && options !== null ) { - collateFetchArgumentsFn.collateKnownProps(options, out); + if ( value.includes('$currentISODate$') ) { + value = value.replaceAll('$currentISODate$', time.toISOString()); } - return out; -} -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 }; + + 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'); } - 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; - }); - }); -} -function preventFetch(...args) { - preventFetchFn(false, ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -30591,217 +43295,94 @@ try { } } catch { } -preventFetch(...args); +trustedSetCookie(...args); }, }; -scriptlets['trusted-prevent-fetch.js'] = { +scriptlets['trusted-set-cookie-reload.js'] = { aliases: [], - +world: 'ISOLATED', requiresTrust: true, +fn: 'trustedSetCookieReload', +dependencies: ["getCookieFn","setCookieFn","safeSelf","trustedSetCookie"], func: function (scriptletGlobals = {}, ...args) { -function proxyApplyFn( - target = '', - handler = '', - options = {} +function getCookieFn( + name = '' ) { - 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 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}`); - } + 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(); } - 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 '{}'; +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); } - if ( directive === 'emptyArr' ) { - return '[]'; + // 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); } - if ( directive === 'emptyStr' ) { - return ''; + + 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 ( 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 ( path === '' ) { path = '/'; } + else if ( path === 'none' ) { path = ''; } + if ( path !== '' && path !== '/' ) { return; } + if ( path === '/' ) { + cookieParts.push('; path=/'); } - 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); + + 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; } - const warXHR = new safe.XMLHttpRequest(); - warXHR.responseType = 'text'; - warXHR.onloadend = ev => { - resolve(ev.target.responseText || ''); - }; - warXHR.open('GET', fullpath.join('')); - warXHR.send(); - }).catch(( ) => ''); + if ( domain ) { + cookieParts.push(`; domain=${domain}`); + } + } + cookieParts.push('; Secure'); + } else if ( /^__(Host|Secure)-/.test(name) ) { + cookieParts.push('; Secure'); } - 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(''); + + try { + document.cookie = cookieParts.join(''); + } catch { } - if ( trusted ) { - return directive; + + const done = getCookieFn(name) === value; + if ( done && options.reload ) { + window.location.reload(); } - return ''; + + return done; } function safeSelf() { if ( safeSelf.safe ) { @@ -30977,123 +43558,57 @@ function safeSelf() { } return safe; } -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 preventFetchFn( - trusted = false, - propsToMatch = '', - responseBody = '', - responseType = '', +function trustedSetCookie( + name = '', + value = '', + offsetExpiresSec = '', + path = '', ...varargs ) { + if ( name === '' ) { return; } + 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 }; - } + const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path); + const time = new Date(); + + if ( value.includes('$now$') ) { + value = value.replaceAll('$now$', time.getTime()); } - 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(); + 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)); } - 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; - }); - }); + expires = time.toUTCString(); + } + + const done = setCookieFn( + true, + name, + value, + expires, + path, + safe.parseVarargs(varargs) + ); + + if ( done ) { + safe.uboLog(logPrefix, 'Done'); + } } -function trustedPreventFetch(...args) { - preventFetchFn(true, ...args); +function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) { + trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -31103,15 +43618,17 @@ try { } } catch { } -trustedPreventFetch(...args); +trustedSetCookieReload(...args); }, }; -scriptlets['freeze-element-property.js'] = { -aliases: [], - +scriptlets['remove-cookie.js'] = { +aliases: ["cookie-remover.js"], +world: 'ISOLATED', requiresTrust: false, +fn: 'removeCookie', +dependencies: ["safeSelf"], func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { @@ -31287,57 +43804,77 @@ function safeSelf() { } return safe; } -function freezeElementProperty( - property = '', - selector = '', - pattern = '' +function removeCookie( + needle = '', + ...varargs ) { + if ( typeof needle !== 'string' ) { return; } 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}`); + 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); }; - 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); + 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', '')}`; + } } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Assigned:\n${a}`); + 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; } - current.value = a; - }, - }); + 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 }); + } }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -31347,16 +43884,123 @@ try { } } catch { } -freezeElementProperty(...args); +removeCookie(...args); }, }; -scriptlets['prevent-innerHTML.js'] = { +scriptlets['set-local-storage-item.js'] = { aliases: [], - +world: 'ISOLATED', requiresTrust: false, +fn: 'setLocalStorageItem', +dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], func: function (scriptletGlobals = {}, ...args) { +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 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); + } +} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -31531,63 +44175,10 @@ function safeSelf() { } return safe; } -function freezeElementProperty( - property = '', - selector = '', - pattern = '' -) { +function setLocalStorageItem(key = '', value = '', ...varargs) { 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; - }, - }); -} -function preventInnerHTML( - selector = '', - pattern = '' -) { - freezeElementProperty('innerHTML', selector, pattern); + const options = safe.parseVarargs(varargs) + setLocalStorageItemFn('local', false, key, value, options); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -31597,16 +44188,123 @@ try { } } catch { } -preventInnerHTML(...args); +setLocalStorageItem(...args); }, }; -scriptlets['prevent-navigation.js'] = { +scriptlets['set-session-storage-item.js'] = { aliases: [], world: 'ISOLATED', requiresTrust: false, +fn: 'setSessionStorageItem', +dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], func: function (scriptletGlobals = {}, ...args) { +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 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); + } +} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -31781,44 +44479,136 @@ function safeSelf() { } return safe; } -function preventNavigation( - pattern = '' +function setSessionStorageItem(key = '', value = '', ...varargs) { + const safe = safeSelf(); + const options = safe.parseVarargs(varargs) + setLocalStorageItemFn('session', false, key, value, options); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); + } +} catch { +} +setSessionStorageItem(...args); +}, +}; + + +scriptlets['trusted-set-local-storage-item.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedSetLocalStorageItem', +dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], +func: function (scriptletGlobals = {}, ...args) { +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 setLocalStorageItemFn( + which = 'local', + trusted = false, + key = '', + value = '', + options = {} ) { - 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 ( 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 ( safe.testPattern(matcher, url) ) { - ev.preventDefault(); - safe.uboLog(logPrefix, `Prevented navigation to ${url}`); + 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; } } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); } -} catch { -} -preventNavigation(...args); -}, -}; + let modified = false; -scriptlets['prevent-setTimeout.js'] = { -aliases: ["no-setTimeout-if.js","nostif.js","setTimeout-defuser.js"], + 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 { -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { + 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); + } +} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -31993,159 +44783,10 @@ function safeSelf() { } return safe; } -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; - } -} -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 preventSetTimeout( - needleRaw = '', - delayRaw = '' -) { +function trustedSetLocalStorageItem(key = '', value = '', ...varargs) { 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(); - }); + const options = safe.parseVarargs(varargs) + setLocalStorageItemFn('local', true, key, value, options); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -32155,16 +44796,123 @@ try { } } catch { } -preventSetTimeout(...args); +trustedSetLocalStorageItem(...args); }, }; -scriptlets['prevent-setInterval.js'] = { -aliases: ["no-setInterval-if.js","nosiif.js","setInterval-defuser.js"], - -requiresTrust: false, +scriptlets['trusted-set-session-storage-item.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedSetSessionStorageItem', +dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], func: function (scriptletGlobals = {}, ...args) { +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 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); + } +} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -32331,167 +45079,18 @@ function safeSelf() { }; bc.postMessage('areyouready?'); } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; -} -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; - } -} -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)); + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); }; } - const proxiedTarget = new Proxy(fn, proxyDetails); - proxyApplyFn.proxies.set(proxiedTarget, fn); - context[prop] = proxiedTarget; + return safe; } -function preventSetInterval( - needleRaw = '', - delayRaw = '' -) { +function trustedSetSessionStorageItem(key = '', value = '', ...varargs) { 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(); - }); + const options = safe.parseVarargs(varargs) + setLocalStorageItemFn('session', true, key, value, options); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -32501,15 +45100,17 @@ try { } } catch { } -preventSetInterval(...args); +trustedSetSessionStorageItem(...args); }, }; -scriptlets['prevent-requestAnimationFrame.js'] = { -aliases: ["no-requestAnimationFrame-if.js","norafif.js"], +scriptlets['abort-on-property-read.js'] = { +aliases: ["aopr.js"], requiresTrust: false, +fn: 'abortOnPropertyRead', +dependencies: ["safeSelf","getRandomTokenFn","getExceptionTokenFn"], func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { @@ -32685,124 +45286,67 @@ function safeSelf() { } return safe; } -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)); +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); } - }; - 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; + }.bind(); + return token; } -function preventRequestAnimationFrame( - needleRaw = '' +function abortOnPropertyRead( + chain = '' ) { + if ( typeof chain !== 'string' ) { return; } + if ( chain === '' ) { return; } 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}`); + 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; } - return context.reflect(); - }); + 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); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -32812,67 +45356,18 @@ try { } } catch { } -preventRequestAnimationFrame(...args); +abortOnPropertyRead(...args); }, }; -scriptlets['set-constant.js'] = { -aliases: ["set.js"], +scriptlets['abort-on-property-write.js'] = { +aliases: ["aopw.js"], requiresTrust: false, +fn: 'abortOnPropertyWrite', +dependencies: ["safeSelf","getRandomTokenFn","getExceptionTokenFn"], func: function (scriptletGlobals = {}, ...args) { -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; -} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -33047,258 +45542,66 @@ function safeSelf() { } return safe; } -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 setConstantFn( - trusted = false, - chain = '', - rawValue = '', - ...varargs -) { - if ( chain === '' ) { return; } +function getRandomTokenFn() { 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); -} -function setConstant( - ...args -) { - setConstantFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { + return safe.String_fromCharCode(Date.now() % 26 + 97) + + safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); } -setConstant(...args); -}, -}; - - -scriptlets['trusted-set-constant.js'] = { -aliases: ["trusted-set.js"], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -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; } +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); } - } else { - return; + }.bind(); + return token; +} +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); } - 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); + delete owner[prop]; + Object.defineProperty(owner, prop, { + set: function() { + safe.uboLog(logPrefix, 'Aborted'); + throw new ReferenceError(exceptionToken); } + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - return value; +} catch { } +abortOnPropertyWrite(...args); +}, +}; + + +scriptlets['adjust-setInterval.js'] = { +aliases: ["nano-setInterval-booster.js","nano-sib.js"], + +requiresTrust: false, +fn: 'adjustSetInterval', +dependencies: ["safeSelf"], +func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -33473,188 +45776,32 @@ function safeSelf() { } return safe; } -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 setConstantFn( - trusted = false, - chain = '', - rawValue = '', - ...varargs +function adjustSetInterval( + needleArg = '', + delayArg = '', + boostArg = '' ) { - if ( chain === '' ) { return; } + if ( typeof needleArg !== 'string' ) { 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; + 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; } - 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); -} -function trustedSetConstant( - ...args -) { - setConstantFn(true, ...args); + return target.apply(thisArg, args); + } + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -33664,67 +45811,18 @@ try { } } catch { } -trustedSetConstant(...args); +adjustSetInterval(...args); }, }; -scriptlets['trusted-replace-argument.js'] = { -aliases: [], +scriptlets['adjust-setTimeout.js'] = { +aliases: ["nano-setTimeout-booster.js","nano-stb.js"], -requiresTrust: true, +requiresTrust: false, +fn: 'adjustSetTimeout', +dependencies: ["safeSelf"], func: function (scriptletGlobals = {}, ...args) { -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; -} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -33884,295 +45982,46 @@ function safeSelf() { 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; -} -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; -} -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); + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; } - 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 }; + }; + bc.postMessage('areyouready?'); } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; } + return safe; } -function trustedReplaceArgument( - propChain = '', - argposRaw = '', - argraw = '', - ...varargs +function adjustSetTimeout( + needleArg = '', + delayArg = '', + boostArg = '' ) { - if ( propChain === '' ) { return; } + if ( typeof needleArg !== 'string' ) { 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 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); } - const argAfter = replacer(argBefore); - if ( argAfter !== argBefore ) { - setArg(context, argAfter); - safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`); - } - return context.reflect(); }); }; try { @@ -34183,15 +46032,17 @@ try { } } catch { } -trustedReplaceArgument(...args); +adjustSetTimeout(...args); }, }; -scriptlets['spoof-css.js'] = { -aliases: [], - +scriptlets['prevent-refresh.js'] = { +aliases: ["refresh-defuser.js"], +world: 'ISOLATED', requiresTrust: false, +fn: 'preventRefresh', +dependencies: ["safeSelf"], func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { @@ -34367,122 +46218,30 @@ function safeSelf() { } return safe; } -function spoofCSS( - selector, - ...args +function preventRefresh( + delay = '' ) { - 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); - } - } + if ( typeof delay !== 'string' ) { return; } 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 logPrefix = safe.makeLogPrefix('prevent-refresh', delay); + const stop = content => { + window.stop(); + safe.uboLog(logPrefix, `Prevented "${content}"`); }; - 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; + 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.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); - }, - }); + self.addEventListener('load', defuse, { capture: true, once: true }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -34492,153 +46251,308 @@ try { } } catch { } -spoofCSS(...args); +preventRefresh(...args); }, }; -scriptlets['prevent-xhr.js'] = { -aliases: ["no-xhr-if.js"], - +scriptlets['remove-class.js'] = { +aliases: ["rc.js"], +world: 'ISOLATED', requiresTrust: false, +fn: 'removeClass', +dependencies: ["safeSelf","runAt","onIdleFn"], func: function (scriptletGlobals = {}, ...args) { -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); +function safeSelf() { + if ( safeSelf.safe ) { + return safeSelf.safe; } - const fn = context[prop]; - if ( typeof fn !== 'function' ) { return; } - if ( proxyApplyFn.CtorContext === undefined ) { - proxyApplyFn.ctorContexts = []; - proxyApplyFn.CtorContext = class { - constructor(...args) { - this.init(...args); + 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 }; } - init(callFn, callArgs) { - this.callFn = callFn; - this.callArgs = callArgs; - return this; + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); } - reflect() { - const r = Reflect.construct(this.callFn, this.callArgs); - this.callFn = this.callArgs = this.private = undefined; - proxyApplyFn.ctorContexts.push(this); - return r; + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; } - static factory(...args) { - return proxyApplyFn.ctorContexts.length !== 0 - ? proxyApplyFn.ctorContexts.pop().init(...args) - : new proxyApplyFn.CtorContext(...args); + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; } - }; - proxyApplyFn.applyContexts = []; - proxyApplyFn.ApplyContext = class { - constructor(...args) { - this.init(...args); + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test(details.re, haystack) === details.expect; } - init(callFn, thisArg, callArgs) { - this.callFn = callFn; - this.thisArg = thisArg; - this.callArgs = callArgs; - return this; + 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); } - 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; + try { + return new RegExp(match[1], match[2] || undefined); } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); + catch { } - }; - 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; + 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}`); + }; } - 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)); + return safe; +} +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; }; - if ( proxyApplyFn.isCtor.get(target) ) { - proxyDetails.construct = function(target, args) { - return handler(proxyApplyFn.CtorContext.factory(target, args)); - }; + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; } - const proxiedTarget = new Proxy(fn, proxyDetails); - proxyApplyFn.proxies.set(proxiedTarget, fn); - context[prop] = proxiedTarget; -} -function parsePropertiesToMatchFn(propsToMatch, implicit = '') { + const onStateChange = ( ) => { + if ( intFromReadyState(document.readyState) < runAt ) { return; } + fn(); + safe.removeEventListener.apply(document, args); + }; 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)); - } + const args = [ 'readystatechange', onStateChange, { capture: true } ]; + safe.addEventListener.apply(document, args); +} +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); } - return needles; + return self.requestAnimationFrame(fn); } -function matchObjectPropertiesFn(propNeedles, ...objs) { +function removeClass( + rawToken = '', + rawSelector = '', + behavior = '' +) { + if ( typeof rawToken !== 'string' ) { return; } + if ( rawToken === '' ) { return; } 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; } + 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 ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); } + 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'); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - return matched; +} catch { } +removeClass(...args); +}, +}; + + +scriptlets['webrtc-if.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'webrtcIf', +dependencies: ["safeSelf"], +func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -34813,259 +46727,258 @@ function safeSelf() { } return safe; } -function generateContentFn(trusted, directive) { +function webrtcIf( + good = '' +) { + if ( typeof good !== 'string' ) { return; } 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; + 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; } + } } - while ( textSize < len ); - return chunks.join(' ').slice(0, len); + neuteredPeerConnections.add(instance); + return false; }; - 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 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); } - 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; + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - return ''; +} catch { } -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; +webrtcIf(...args); +}, +}; + + +scriptlets['prevent-window-open.js'] = { +aliases: ["nowoif.js","no-window-open-if.js","window.open-defuser.js"], + +requiresTrust: false, +fn: 'noWindowOpenIf', +dependencies: ["safeSelf","proxyApplyFn"], +func: function (scriptletGlobals = {}, ...args) { +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 }; } - 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}`); + 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; } } - if ( out.length !== 0 ) { out.push(''); } - return out.join('\r\n'); - }); -} -function preventXhr(...args) { - preventXhrFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + 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}`); + }; } -} catch { + return safe; } -preventXhr(...args); -}, -}; - - -scriptlets['trusted-prevent-xhr.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', handler = '', @@ -35164,45 +47077,111 @@ function proxyApplyFn( proxyApplyFn.proxies.set(proxiedTarget, fn); context[prop] = proxiedTarget; } -function parsePropertiesToMatchFn(propsToMatch, implicit = '') { +function noWindowOpenIf( + pattern = '', + delay = '', + decoy = '' +) { 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)); - } + const logPrefix = safe.makeLogPrefix('no-window-open-if', pattern, delay, decoy); + const targetMatchResult = pattern.startsWith('!') === false; + if ( targetMatchResult === false ) { + pattern = pattern.slice(1); } - 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; } + 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(', ')})`); } - if ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); + 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; + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - return matched; +} catch { } +noWindowOpenIf(...args); +}, +}; + + +scriptlets['close-window.js'] = { +aliases: ["window-close-if.js"], +world: 'ISOLATED', +requiresTrust: false, +fn: 'closeWindow', +dependencies: ["safeSelf"], +func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -35377,258 +47356,222 @@ function safeSelf() { } return safe; } -function generateContentFn(trusted, directive) { +function closeWindow( + arg1 = '' +) { + if ( typeof arg1 !== 'string' ) { return; } 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(( ) => ''); + let subject = ''; + if ( /^\/.*\/$/.test(arg1) ) { + subject = window.location.href; + } else if ( arg1 !== '' ) { + subject = `${window.location.pathname}${window.location.search}`; } - 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(''); + try { + const re = safe.patternToRegex(arg1); + if ( re.test(subject) ) { + window.close(); + } + } catch(ex) { + console.log(ex); } - if ( trusted ) { - return directive; +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } - return ''; +} catch { } -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; +closeWindow(...args); +}, +}; + + +scriptlets['window.name-defuser.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'windowNameDefuser', +dependencies: [], +func: function (scriptletGlobals = {}, ...args) { + +function windowNameDefuser() { + if ( window === window.top ) { + window.name = ''; + } +}; +windowNameDefuser(...args); +}, +}; + + +scriptlets['overlay-buster.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'overlayBuster', +dependencies: [], +func: function (scriptletGlobals = {}, ...args) { + +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; } - 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(); + 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; } - 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(); + if ( (Date.now() - tstart) < ttl ) { + delay = Math.min(delay + delayStep, 1000); + setTimeout(buster, delay); } - const out = []; - for ( const [ name, value ] of Object.entries(xhrDetails.headers) ) { - if ( !value ) { continue; } - out.push(`${name}: ${value}`); + }; + var domReady = function(ev) { + if ( ev ) { + document.removeEventListener(ev.type, domReady); } - if ( out.length !== 0 ) { out.push(''); } - return out.join('\r\n'); + tstart = Date.now(); + setTimeout(buster, delay); + }; + if ( document.readyState === 'loading' ) { + document.addEventListener('DOMContentLoaded', domReady); + } else { + domReady(); + } +}; +overlayBuster(...args); +}, +}; + + +scriptlets['alert-buster.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'alertBuster', +dependencies: [], +func: function (scriptletGlobals = {}, ...args) { + +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); + }, }); -} -function trustedPreventXhr(...args) { - preventXhrFn(true, ...args); }; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); +alertBuster(...args); +}, +}; + + +scriptlets['nowebrtc.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'noWebrtc', +dependencies: [], +func: function (scriptletGlobals = {}, ...args) { + +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); } -} catch { -} -trustedPreventXhr(...args); +}; +noWebrtc(...args); }, }; -scriptlets['mpegdash-prune.js'] = { +scriptlets['disable-newtab-links.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'disableNewtabLinks', +dependencies: [], +func: function (scriptletGlobals = {}, ...args) { + +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 }); +}; +disableNewtabLinks(...args); +}, +}; + + +scriptlets['xml-prune.js'] = { aliases: [], requiresTrust: false, +fn: 'xmlPrune', +dependencies: ["proxyToStringFn","parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","modifyXhrResponseFn"], func: function (scriptletGlobals = {}, ...args) { function proxyToStringFn(proxiedFn, nativeFn) { if ( proxyToStringFn.proxies === undefined ) { @@ -35907,20 +47850,25 @@ function modifyXhrResponseFn( proxyToStringFn(TrappedXMLHttpRequest, NativeXMLHttpRequest); self.XMLHttpRequest = TrappedXMLHttpRequest; } -function mpegdashPrune( +function xmlPrune( selector = '', - propsToMatch = '' + selectorCheck = '', + urlPattern = '', + ...varargs ) { if ( typeof selector !== 'string' ) { return; } if ( selector === '' ) { return; } const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('mpegdash-prune', selector, propsToMatch); + const logPrefix = safe.makeLogPrefix('xml-prune', selector, selectorCheck, urlPattern); + const reUrl = safe.patternToRegex(urlPattern); + const extraArgs = safe.parseVarargs(varargs); const queryAll = (xmlDoc, selector) => { - if ( selector.startsWith('xpath:') === false ) { + const isXpath = /^xpath\(.+\)$/.test(selector); + if ( isXpath === false ) { return Array.from(xmlDoc.querySelectorAll(selector)); } const xpr = xmlDoc.evaluate( - selector.slice(6), + selector.slice(6, -1), xmlDoc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, @@ -35933,93 +47881,26 @@ function mpegdashPrune( } 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 === '' ) { + 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, `Patching ${items.length} items`); + safe.uboLog(logPrefix, `Removing ${items.length} items`); for ( const item of items ) { - if ( item.nodeType !== 1 ) { continue; } - item.setAttribute('duration', 'PT0S'); + 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`); } - fixTimeAttributes(xmlDoc); } catch(ex) { safe.uboErr(logPrefix, `Error: ${ex}`); } @@ -36039,344 +47920,47 @@ function mpegdashPrune( } return text; }; - modifyXhrResponseFn(propsToMatch, (xhr, before) => { - if ( before instanceof XMLDocument ) { - return pruneFromDoc(before); - } - if ( typeof before === 'string' ) { - return pruneFromText(before); - } - return before; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -mpegdashPrune(...args); -}, -}; - - -scriptlets['set-cookie.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -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(); - } -} -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; -} -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; + 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; } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; -} -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 = '', - 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'); - } + 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; + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -36386,93 +47970,18 @@ try { } } catch { } -setCookie(...args); +xmlPrune(...args); }, }; -scriptlets['set-cookie-reload.js'] = { +scriptlets['m3u-prune.js'] = { aliases: [], -world: 'ISOLATED', + requiresTrust: false, +fn: 'm3uPrune', +dependencies: ["safeSelf","proxyApplyFn"], func: function (scriptletGlobals = {}, ...args) { -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(); - } -} -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; -} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -36647,164 +48156,282 @@ function safeSelf() { } return safe; } -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 = '', - path = '', - ...varargs +function proxyApplyFn( + target = '', + handler = '', + options = {} ) { - 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; } + 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 done = setCookieFn( - false, - name, - value, - '', - path, - safe.parseVarargs(varargs) - ); - - if ( done ) { - safe.uboLog(logPrefix, 'Done'); + 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; + } } -} -function setCookieReload(name, value, path, ...args) { - setCookie(name, value, path, 'reload', '1', ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); } -} catch { -} -setCookieReload(...args); -}, -}; - - -scriptlets['trusted-set-cookie.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -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(); + 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 setCookieFn( - trusted = false, - name = '', - value = '', - expires = '', - path = '', - options = {}, +function m3uPrune( + m3uPattern = '', + urlPattern = '' ) { - // 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 ( 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; } } - if ( domain ) { - cookieParts.push(`; domain=${domain}`); + 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); } - cookieParts.push('; Secure'); - } else if ( /^__(Host|Secure)-/.test(name) ) { - cookieParts.push('; Secure'); + }); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } +} catch { +} +m3uPrune(...args); +}, +}; - try { - document.cookie = cookieParts.join(''); - } catch { - } - const done = getCookieFn(name) === value; - if ( done && options.reload ) { - window.location.reload(); - } +scriptlets['call-nothrow.js'] = { +aliases: [], - return done; -} +requiresTrust: false, +fn: 'callNothrow', +dependencies: ["safeSelf"], +func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -36979,54 +48606,33 @@ function safeSelf() { } return safe; } -function trustedSetCookie( - name = '', - value = '', - offsetExpiresSec = '', - path = '', - ...varargs +function callNothrow( + chain = '' ) { - if ( name === '' ) { return; } - + if ( typeof chain !== 'string' ) { return; } + if ( chain === '' ) { 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'); + 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; + }, + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -37036,92 +48642,45 @@ try { } } catch { } -trustedSetCookie(...args); +callNothrow(...args); }, }; -scriptlets['trusted-set-cookie-reload.js'] = { -aliases: [], +scriptlets['remove-node-text.js'] = { +aliases: ["rmnt.js"], world: 'ISOLATED', -requiresTrust: true, +requiresTrust: false, +fn: 'removeNodeText', +dependencies: ["runAt","safeSelf","getRandomTokenFn","replaceNodeTextFn"], func: function (scriptletGlobals = {}, ...args) { -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(); - } -} -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}`); - } +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]; } - 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 0; + }; + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; } - - return done; + 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 safeSelf() { if ( safeSelf.safe ) { @@ -37297,57 +48856,131 @@ function safeSelf() { } return safe; } -function trustedSetCookie( - name = '', - value = '', - offsetExpiresSec = '', - path = '', +function getRandomTokenFn() { + const safe = safeSelf(); + return safe.String_fromCharCode(Date.now() % 26 + 97) + + safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); +} +function replaceNodeTextFn( + nodeName = '', + pattern = '', + replacement = '', ...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)); + 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); + } + } } - expires = time.toUTCString(); - } - - const done = setCookieFn( - true, - name, - value, - expires, - path, - safe.parseVarargs(varargs) - ); - - if ( done ) { - safe.uboLog(logPrefix, 'Done'); + 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 trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) { - trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args); +function removeNodeText( + nodeName, + includes, + ...extraArgs +) { + replaceNodeTextFn(nodeName, '', '', 'includes', includes || '', ...extraArgs); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -37357,15 +48990,17 @@ try { } } catch { } -trustedSetCookieReload(...args); +removeNodeText(...args); }, }; -scriptlets['remove-cookie.js'] = { -aliases: ["cookie-remover.js"], -world: 'ISOLATED', +scriptlets['prevent-canvas.js'] = { +aliases: [], + requiresTrust: false, +fn: 'preventCanvas', +dependencies: ["safeSelf"], func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { @@ -37515,103 +49150,44 @@ function safeSelf() { }; 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; -} -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; + 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; } - }); - }; - 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 }); + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; } + return safe; +} +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); + } + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -37621,120 +49197,189 @@ try { } } catch { } -removeCookie(...args); +preventCanvas(...args); }, }; -scriptlets['set-local-storage-item.js'] = { +scriptlets['multiup.js'] = { aliases: [], world: 'ISOLATED', requiresTrust: false, +fn: 'multiup', +dependencies: [], func: function (scriptletGlobals = {}, ...args) { -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 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(), - ]; +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 }); +}; +multiup(...args); +}, +}; - 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; +scriptlets['break-on-call.js'] = { +aliases: [], - 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); } +requiresTrust: false, +fn: 'breakOnCall', +dependencies: ["proxyApplyFn"], +func: function (scriptletGlobals = {}, ...args) { +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); } - modified = toRemove.length !== 0; - for ( const key of toRemove ) { - storage.removeItem(key); + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; } - } else { - - const before = storage.getItem(key); - const after = `${value}`; - modified = after !== before; - if ( modified ) { - storage.setItem(key, after); + 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; } - } catch { } + 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 breakOnCall(target) { + proxyApplyFn(target, function(context) { + debugger; // eslint-disable-line no-debugger + return context.reflect(); + }); +}; +breakOnCall(...args); +}, +}; - if ( modified && typeof options.reload === 'number' ) { - setTimeout(( ) => { window.location.reload(); }, options.reload); + +scriptlets['trusted-replace-node-text.js'] = { +aliases: ["trusted-rpnt.js","replace-node-text.js","rpnt.js"], +world: 'ISOLATED', +requiresTrust: true, +fn: 'replaceNodeText', +dependencies: ["runAt","safeSelf","getRandomTokenFn","replaceNodeTextFn"], +func: function (scriptletGlobals = {}, ...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 safeSelf() { if ( safeSelf.safe ) { @@ -37910,133 +49555,191 @@ function safeSelf() { } return safe; } -function setLocalStorageItem(key = '', value = '', ...varargs) { +function getRandomTokenFn() { const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('local', false, key, value, options); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -setLocalStorageItem(...args); -}, -}; - - -scriptlets['set-session-storage-item.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -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', - ]; + return safe.String_fromCharCode(Date.now() % 26 + 97) + + safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); } -function setLocalStorageItemFn( - which = 'local', - trusted = false, - key = '', - value = '', - options = {} +function replaceNodeTextFn( + nodeName = '', + pattern = '', + replacement = '', + ...varargs ) { - 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()); + 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); + } + } } - if ( value.includes('$currentDate$') ) { - value = value.replaceAll('$currentDate$', `${Date()}`); + 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 ( value.includes('$currentISODate$') ) { - value = value.replaceAll('$currentISODate$', (new Date()).toISOString()); + if ( reExcludes ) { + reExcludes.lastIndex = 0; + if ( safe.RegExp_test(reExcludes, before) ) { return; } } - } 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; } + 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 replaceNodeText( + nodeName, + pattern, + replacement, + ...extraArgs +) { + replaceNodeTextFn(nodeName, pattern, replacement, ...extraArgs); +}; +try { + const key = Symbol.for('safeSelf.1.75.1b1'); + safeSelf.safe = globalThis[key]; + if ( safeSelf.safe === undefined ) { + Object.defineProperty(globalThis, key, { value: safeSelf() }); } +} catch { +} +replaceNodeText(...args); +}, +}; - 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 { +scriptlets['trusted-replace-fetch-response.js'] = { +aliases: ["trusted-rpfr.js"], - const before = storage.getItem(key); - const after = `${value}`; - modified = after !== before; - if ( modified ) { - storage.setItem(key, after); - } +requiresTrust: true, +fn: 'trustedReplaceFetchResponse', +dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","collateFetchArgumentsFn","replaceFetchResponseFn"], +func: function (scriptletGlobals = {}, ...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)); } - } catch { } - - if ( modified && typeof options.reload === 'number' ) { - setTimeout(( ) => { window.location.reload(); }, options.reload); + 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 safeSelf() { if ( safeSelf.safe ) { @@ -38212,10 +49915,101 @@ function safeSelf() { } return safe; } -function setSessionStorageItem(key = '', value = '', ...varargs) { +function collateFetchArgumentsFn(resource, options) { const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('session', false, key, value, options); + 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 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); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -38225,120 +50019,37 @@ try { } } catch { } -setSessionStorageItem(...args); +trustedReplaceFetchResponse(...args); }, }; -scriptlets['trusted-set-local-storage-item.js'] = { +scriptlets['trusted-replace-xhr-response.js'] = { aliases: [], -world: 'ISOLATED', + requiresTrust: true, +fn: 'trustedReplaceXhrResponse', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn"], func: function (scriptletGlobals = {}, ...args) { -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 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; } +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; } - } - - 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); - } + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); } - } catch { - } - - if ( modified && typeof options.reload === 'number' ) { - setTimeout(( ) => { window.location.reload(); }, options.reload); } + return needles; } function safeSelf() { if ( safeSelf.safe ) { @@ -38514,10 +50225,94 @@ function safeSelf() { } return safe; } -function trustedSetLocalStorageItem(key = '', value = '', ...varargs) { +function matchObjectPropertiesFn(propNeedles, ...objs) { const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('local', true, key, value, options); + 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 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; + } + }; }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -38527,120 +50322,138 @@ try { } } catch { } -trustedSetLocalStorageItem(...args); +trustedReplaceXhrResponse(...args); }, }; -scriptlets['trusted-set-session-storage-item.js'] = { +scriptlets['trusted-click-element.js'] = { aliases: [], world: 'ISOLATED', requiresTrust: true, +fn: 'trustedClickElement', +dependencies: ["runAtHtmlElementFn","onIdleFn","offIdleFn","lookupElementsFn","getAllLocalStorageFn","safeSelf","getAllCookiesFn"], func: function (scriptletGlobals = {}, ...args) { -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 runAtHtmlElementFn(fn) { + if ( document.documentElement ) { + fn(); + return; + } + const observer = new MutationObserver(( ) => { + observer.disconnect(); + fn(); + }); + observer.observe(document, { childList: true }); } -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 = '{}'; +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); } - - 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; } - } + return self.requestAnimationFrame(fn); +} +function offIdleFn(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); } - - 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); } + return self.cancelAnimationFrame(id); +} +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); } - modified = toRemove.length !== 0; - for ( const key of toRemove ) { - storage.removeItem(key); + 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)); } - } else { - - const before = storage.getItem(key); - const after = `${value}`; - modified = after !== before; - if ( modified ) { - storage.setItem(key, after); + 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); } - } - } catch { + 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 ( modified && typeof options.reload === 'number' ) { - setTimeout(( ) => { window.location.reload(); }, options.reload); + if ( until === 0 ) { + return lookupElementsFn.lookup(directive); + } + return new Promise(resolve => { + lookupElementsFn.lookupAsync({ directive, until, resolve }); + }); +} +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 safeSelf() { if ( safeSelf.safe ) { @@ -38816,10 +50629,133 @@ function safeSelf() { } return safe; } -function trustedSetSessionStorageItem(key = '', value = '', ...varargs) { +function getAllCookiesFn() { const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('session', true, key, value, options); + 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 = '', + 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); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -38829,15 +50765,17 @@ try { } } catch { } -trustedSetSessionStorageItem(...args); +trustedClickElement(...args); }, }; -scriptlets['abort-on-property-read.js'] = { -aliases: ["aopr.js"], +scriptlets['trusted-replace-outbound-text.js'] = { +aliases: [], -requiresTrust: false, +requiresTrust: true, +fn: 'trustedReplaceOutboundText', +dependencies: ["safeSelf","proxyApplyFn"], func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { @@ -39013,67 +50951,144 @@ function safeSelf() { } 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); +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; } - }.bind(); - return token; + } + 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 abortOnPropertyRead( - chain = '' +function trustedReplaceOutboundText( + propChain = '', + rawPattern = '', + rawReplacement = '', + ...varargs ) { - if ( typeof chain !== 'string' ) { return; } - if ( chain === '' ) { return; } + if ( propChain === '' ) { 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 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; } } - const prop = chain.slice(0, pos); - let v = owner[prop]; - chain = chain.slice(pos + 1); - if ( v ) { - makeProxy(v, chain); - return; + if ( rawPattern === '' ) { + safe.uboLog(logPrefix, 'Decoded outbound text:\n', textBefore); + return encodedTextBefore; } - 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); + 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; + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -39083,16 +51098,116 @@ try { } } catch { } -abortOnPropertyRead(...args); -}, -}; - - -scriptlets['abort-on-property-write.js'] = { -aliases: ["aopw.js"], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { +trustedReplaceOutboundText(...args); +}, +}; + + +scriptlets['trusted-suppress-native-method.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedSuppressNativeMethod', +dependencies: ["proxyApplyFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn"], +func: function (scriptletGlobals = {}, ...args) { +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 safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -39283,27 +51398,114 @@ function getExceptionTokenFn() { }.bind(); return token; } -function abortOnPropertyWrite( - prop = '' +function matchesStackTraceFn( + needleDetails, + logLevel = '' ) { - 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); + 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()); } - delete owner[prop]; - Object.defineProperty(owner, prop, { - set: function() { - safe.uboLog(logPrefix, 'Aborted'); - throw new ReferenceError(exceptionToken); + 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; +} +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(); } }); }; @@ -39315,15 +51517,17 @@ try { } } catch { } -abortOnPropertyWrite(...args); +trustedSuppressNativeMethod(...args); }, }; -scriptlets['adjust-setInterval.js'] = { -aliases: ["nano-setInterval-booster.js","nano-sib.js"], +scriptlets['trusted-prevent-dom-bypass.js'] = { +aliases: [], -requiresTrust: false, +requiresTrust: true, +fn: 'trustedPreventDomBypass', +dependencies: ["safeSelf","proxyApplyFn"], func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { @@ -39499,31 +51703,142 @@ function safeSelf() { } return safe; } -function adjustSetInterval( - needleArg = '', - delayArg = '', - boostArg = '' +function proxyApplyFn( + target = '', + handler = '', + options = {} ) { - if ( typeof needleArg !== 'string' ) { return; } + 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 trustedPreventDomBypass( + methodPath = '', + targetProp = '' +) { + if ( methodPath === '' ) { 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; + 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 target.apply(thisArg, args); } + return r; }); }; try { @@ -39534,235 +51849,69 @@ try { } } catch { } -adjustSetInterval(...args); +trustedPreventDomBypass(...args); }, }; -scriptlets['adjust-setTimeout.js'] = { -aliases: ["nano-setTimeout-booster.js","nano-stb.js"], +scriptlets['trusted-override-element-method.js'] = { +aliases: [], -requiresTrust: false, +requiresTrust: true, +fn: 'trustedOverrideElementMethod', +dependencies: ["validateConstantFn","safeSelf","proxyApplyFn"], func: function (scriptletGlobals = {}, ...args) { -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; -} -function adjustSetTimeout( - needleArg = '', - delayArg = '', - boostArg = '' -) { - if ( typeof needleArg !== 'string' ) { return; } +function validateConstantFn(trusted, raw, extraArgs = {}) { 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); + 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; } } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + } else { + return; } -} catch { + 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; } -adjustSetTimeout(...args); -}, -}; - - -scriptlets['prevent-refresh.js'] = { -aliases: ["refresh-defuser.js"], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -39937,30 +52086,136 @@ function safeSelf() { } return safe; } -function preventRefresh( - delay = '' +function proxyApplyFn( + target = '', + handler = '', + options = {} ) { - 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); + 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)); } }; - self.addEventListener('load', defuse, { capture: true, once: true }); + 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 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); + }); }; try { const key = Symbol.for('safeSelf.1.75.1b1'); @@ -39970,16 +52225,96 @@ try { } } catch { } -preventRefresh(...args); +trustedOverrideElementMethod(...args); }, }; -scriptlets['remove-class.js'] = { -aliases: ["rc.js"], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { +// For chrome.scripting.executeScript(), which takes a function and JSON arguments but no code. +export function run(scriptletGlobals, calls) { +function runAtHtmlElementFn(fn) { + if ( document.documentElement ) { + fn(); + return; + } + const observer = new MutationObserver(( ) => { + observer.disconnect(); + fn(); + }); + observer.observe(document, { childList: true }); +} +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; +} function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -40154,6 +52489,89 @@ function safeSelf() { } 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 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 }); +} +function abortCurrentScript(...args) { + runAtHtmlElementFn(( ) => { + abortCurrentScriptFn(...args); + }); +} function runAt(fn, when) { const intFromReadyState = state => { const targets = { @@ -40182,1115 +52600,1534 @@ function runAt(fn, when) { const args = [ 'readystatechange', onStateChange, { capture: true } ]; safe.addEventListener.apply(document, args); } +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'); +} +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); +} +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); +} function onIdleFn(fn, options) { if ( self.requestIdleCallback ) { return self.requestIdleCallback(fn, options); } return self.requestAnimationFrame(fn); } -function removeClass( +function offIdleFn(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); +} +function removeAttr( rawToken = '', rawSelector = '', - behavior = '' + behavior = '', + ...varargs ) { if ( typeof rawToken !== 'string' ) { return; } if ( rawToken === '' ) { return; } const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('remove-class', rawToken, rawSelector, behavior); + const logPrefix = safe.makeLogPrefix('remove-attr', + rawToken, rawSelector, behavior, ...varargs + ); const tokens = safe.String_split.call(rawToken, /\s*\|\s*/); - const selector = tokens - .map(a => `${rawSelector}.${CSS.escape(a)}`) - .join(','); + 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 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 { + const rmattrFromNode = node => { + for ( const attr of tokens ) { + if ( node.hasAttribute(attr) === false ) { continue; } + node.removeAttribute(attr); + safe.uboLog(logPrefix, `Removed attribute '${attr}'`); } - if ( mustStay ) { return; } - if ( document.readyState !== 'complete' ) { return; } - observer.disconnect(); + }; + 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 => { - 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 ( 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)); + } } - for ( let j = 0; j < removedNodes.length && skip; j++ ) { - if ( removedNodes[j].nodeType === 1 ) { skip = false; break; } + if ( lazily ) { return; } + for ( const node of removedNodes ) { + if ( node.nodeType !== 1 ) { continue; } + if ( node.matches(selector) ) { + rmattrFromNode(node); + } } } - if ( skip ) { return; } - timer = onIdleFn(rmclass, { timeout: 67 }); }; - const observer = new MutationObserver(mutationHandler); + 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 = ( ) => { - rmclass(); - observer.observe(document, { + rmattr(); + if ( /\bstay\b/.test(behavior) === false ) { + if ( options.quitAfter === undefined ) { return; } + } + start.observer = new MutationObserver(mutationHandler); + start.observer.observe(document, { attributes: true, - attributeFilter: [ 'class' ], + attributeFilter: tokens, childList: true, subtree: true, }); + if ( options.quitAfter ) { + runAt(( ) => { + self.setTimeout(stop, options.quitAfter * 1000); + }, 'load'); + } }; - runAt(( ) => { - start(); - }, /\bcomplete\b/.test(behavior) ? 'idle' : 'loading'); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { + runAt(( ) => { start(); }, safe.String_split.call(behavior, /\s+/)); } -removeClass(...args); -}, -}; - - -scriptlets['webrtc-if.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; +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 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 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'); + } } - const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); - if ( match !== null ) { - return { - re: new this.RegExp( - match[1], - match[2] || options.flags - ), - expect, - }; + } + observer.observe(document, observerOptions); + }; + runAt(start, extraArgs.runAt || 'loading'); +} +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; } - if ( options.flags !== undefined ) { - return { - re: new this.RegExp(this.escapeRegexChars(pattern), - options.flags - ), - expect, - }; + // 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; } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + // Enforce https + if ( c0 === 0x2B && step === '+https' ) { // + + const s = urlin.replace(/^https?:\/\//, ''); + if ( /^[\w-]:\/\//.test(s) ) { return; } + urlout = `https://${s}`; + continue; } - 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); + // 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; + } } - try { - return new RegExp(match[1], match[2] || undefined); + // 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; } - catch { + // 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; } - 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); - }, + // 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 { + } +} +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; }; - 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; } + 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 { } - lastLogType = type; - lastLogText = text; - lastLogTime = Date.now(); - return text; + return ''; }; - 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; + 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; } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; + 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'); +} +function proxyApplyConfig(config = '') { + try { + if ( typeof proxyApplyFn !== 'function' ) { return; } + config = JSON.parse(config); + if ( typeof config !== 'object' ) { return; } + Object.assign(proxyApplyFn, config); + } catch { } - return safe; } -function webrtcIf( - good = '' +function proxyApplyFn( + target = '', + handler = '', + options = {} ) { - 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; } - } + 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); } - if ( typeof server.username === 'string' ) { - if ( reGood.test(server.username) ) { return true; } + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; } - if ( typeof server.credential === 'string' ) { - if ( reGood.test(server.credential) ) { return true; } + 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)); } - 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); + 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) { + 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); } - return Reflect.construct(target, args); + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = this.v2; + this.#compiled = r; } -} catch { -} -webrtcIf(...args); -}, -}; - - -scriptlets['prevent-window-open.js'] = { -aliases: ["nowoif.js","no-window-open-if.js","window.open-defuser.js"], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; } - 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 }; + 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 expect = (options.canNegate !== true || pattern.startsWith('!') === false); - if ( expect === false ) { - pattern = pattern.slice(1); + } + 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; } - const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); - if ( match !== null ) { - return { - re: new this.RegExp( - match[1], - match[2] || options.flags - ), - expect, - }; + // 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 ( options.flags !== undefined ) { - return { - re: new this.RegExp(this.escapeRegexChars(pattern), - options.flags - ), - expect, - }; + 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; } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + 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; } - 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); + 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; } - try { - return new RegExp(match[1], match[2] || undefined); + // 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; } - catch { + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; } - 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; } + 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; } - 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; + 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 'setScriptletLogLevelToOne': - safe.logLevel = 1; + 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 'setScriptletLogLevelToTwo': - safe.logLevel = 2; + case this.#CHILDREN: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } + resultset = this.#getMatches(resultset, step); 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; -} -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); + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } + default: + break; + } + } + return resultset; } - const fn = context[prop]; - if ( typeof fn !== 'function' ) { return; } - if ( proxyApplyFn.CtorContext === undefined ) { - proxyApplyFn.ctorContexts = []; - proxyApplyFn.CtorContext = class { - constructor(...args) { - this.init(...args); + #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; } - init(callFn, callArgs) { - this.callFn = callFn; - this.callArgs = callArgs; - return this; + 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 ]); + } } - reflect() { - const r = Reflect.construct(this.callFn, this.callArgs); - this.callFn = this.callArgs = this.private = undefined; - proxyApplyFn.ctorContexts.push(this); - return r; + 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 ]); + } } - static factory(...args) { - return proxyApplyFn.ctorContexts.length !== 0 - ? proxyApplyFn.ctorContexts.pop().init(...args) - : new proxyApplyFn.CtorContext(...args); + } + 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); } - }; - proxyApplyFn.applyContexts = []; - proxyApplyFn.ApplyContext = class { - constructor(...args) { - this.init(...args); + 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); } - init(callFn, thisArg, callArgs) { - this.callFn = callFn; - this.thisArg = thisArg; - this.callArgs = callArgs; + 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; } - 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; + if ( c0 === 0x2C /* , */ ) { + if ( needIdentifier ) { return; } + i += 1; + needIdentifier = true; + continue; } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); + 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; } - }; - 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); + 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]; } }); - proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); - Function.prototype.toString = proxiedToString; + 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; } } - if ( proxyApplyFn.isCtor.has(target) === false ) { - proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); +} +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'); } - const proxyDetails = { - apply(target, thisArg, args) { - return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); + 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; + }); +} +function editOutboundObject(propChain = '', jsonq = '') { + editOutboundObjectFn(false, propChain, jsonq); +} +function trustedEditOutboundObject(propChain = '', jsonq = '') { + editOutboundObjectFn(true, propChain, jsonq); +} +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; + }); +} +function jsonEdit(jsonq = '', ...varargs) { + jsonEditFn(false, jsonq, ...varargs); +} +function trustedJsonEdit(jsonq = '', ...varargs) { + jsonEditFn(true, jsonq, ...varargs); +} +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; + } } - }; - 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; + return context.reflect(); + }); } -function noWindowOpenIf( - pattern = '', - delay = '', - decoy = '' +function editInboundObject(propChain = '', argPos = '', jsonq = '') { + editInboundObjectFn(false, propChain, argPos, jsonq); +} +function trustedEditInboundObject(propChain = '', argPos = '', jsonq = '') { + editInboundObjectFn(true, propChain, argPos, jsonq); +} +function editThisObjectFn( + trusted = false, + propChain = '', + jsonq = '', + order = '' ) { + if ( propChain === '' ) { return; } 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 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 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 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)}`); }; - 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); - }, - }); + proxyApplyFn(propChain, function(context) { + const { thisArg } = context; + let r; + if ( order === 'after' ) { + r = context.reflect(); } - 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); - }, - }); + editObj(thisArg); + if ( order !== 'after' ) { + r = context.reflect(); } - return popup; + return r; }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { } -noWindowOpenIf(...args); -}, -}; - - -scriptlets['close-window.js'] = { -aliases: ["window-close-if.js"], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; +function editThisObject(...args) { + editThisObjectFn(false, ...args); +} +function trustedEditThisObject(...args) { + editThisObjectFn(true, ...args); +} +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 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 { + 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 /^/; - }, - 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); + return currentValue; }, - }; - 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; } + set: function(a) { + currentValue = a; } - 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; + }); } -function closeWindow( - arg1 = '' +function editObjectOnGetter(...args) { + editObjectOnGetterFn(false, ...args); +} +function trustedEditObjectOnGetter(...args) { + editObjectOnGetterFn(true, ...args); +} +function editObjectOnSetterFn( + trusted = false, + propChain = '', + jsonq = '', ) { - if ( typeof arg1 !== 'string' ) { return; } + if ( propChain === '' ) { return; } const safe = safeSelf(); - let subject = ''; - if ( /^\/.*\/$/.test(arg1) ) { - subject = window.location.href; - } else if ( arg1 !== '' ) { - subject = `${window.location.pathname}${window.location.search}`; + 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'); } - try { - const re = safe.patternToRegex(arg1); - if ( re.test(subject) ) { - window.close(); + 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); + } } - } catch(ex) { - console.log(ex); - } -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + }); + if ( currentValue instanceof Object ) { + editObj(currentValue); } -} catch { } -closeWindow(...args); -}, -}; - - -scriptlets['window.name-defuser.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { - -function windowNameDefuser() { - if ( window === window.top ) { - window.name = ''; +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); + }); } -}; -windowNameDefuser(...args); -}, -}; - - -scriptlets['overlay-buster.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { - -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; + 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; } - 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; + 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); } - 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(); - } -}; -overlayBuster(...args); -}, -}; - - -scriptlets['alert-buster.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { - -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); + if ( details.observer === undefined ) { + details.observer = new MutationObserver(( ) => { + lookupElementsFn.lookupAsync(details); + }); + details.observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }); } - return Reflect.get(target, prop); - }, + 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 }); }); -}; -alertBuster(...args); -}, -}; - - -scriptlets['nowebrtc.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { - -function noWebrtc() { - var rtcName = window.RTCPeerConnection ? 'RTCPeerConnection' : ( - window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : '' +} +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 ); - 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]'; + 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'); } }; - var z = window[rtcName]; - window[rtcName] = pc.bind(window); - if ( z.prototype ) { - z.prototype.createDataChannel = function() { - return { - close: function() {}, - send: function() {} - }; - }.bind(null); - } -}; -noWebrtc(...args); -}, -}; - - -scriptlets['disable-newtab-links.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { - -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; + 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); } - }, { capture: true }); -}; -disableNewtabLinks(...args); -}, -}; - - -scriptlets['xml-prune.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...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); + await sleepFn(); + } while ( Date.now() < until ); +} +function editElementObject(...args) { + editElementObjectFn(false, ...args); +} +function trustedEditElementObject(...args) { + editElementObjectFn(true, ...args); } function parsePropertiesToMatchFn(propsToMatch, implicit = '') { const safe = safeSelf(); @@ -41312,554 +54149,681 @@ function parsePropertiesToMatchFn(propsToMatch, implicit = '') { } return needles; } -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; +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}`); + } } - 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 }; + return matched; +} +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); } - const expect = (options.canNegate !== true || pattern.startsWith('!') === false); - if ( expect === false ) { - pattern = pattern.slice(1); + 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; } - const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); - if ( match !== null ) { - return { - re: new this.RegExp( - match[1], - match[2] || options.flags - ), - expect, - }; + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; } - if ( options.flags !== undefined ) { - return { - re: new this.RegExp(this.escapeRegexChars(pattern), - options.flags - ), - expect, - }; + let obj; + if ( typeof innerResponse === 'object' ) { + obj = innerResponse; + } else if ( typeof innerResponse === 'string' ) { + try { obj = safe.JSON_parse(innerResponse); } catch { } } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + if ( typeof obj !== 'object' || obj === null ) { + return (xhrDetails.response = innerResponse); } - 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); + const objAfter = jsonp.apply(obj); + if ( objAfter === undefined ) { + return (xhrDetails.response = innerResponse); } - try { - return new RegExp(match[1], match[2] || undefined); + 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; + } + }; +} +function jsonEditXhrResponse(jsonq = '', ...args) { + jsonEditXhrResponseFn(false, jsonq, ...args); +} +function trustedJsonEditXhrResponse(jsonq = '', ...args) { + jsonEditXhrResponseFn(true, jsonq, ...args); +} +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); } - catch { + return super.open(method, url, ...args); + } + send(body) { + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails ) { + body = this.#filterBody(body) || body; } - 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; } + super.send(body); } - 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 }); + #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}`); } - 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; + return body; + } + }; +} +function jsonEditXhrRequest(jsonq = '', ...args) { + jsonEditXhrRequestFn(false, 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]; } }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; } - return safe; + 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 matchObjectPropertiesFn(propNeedles, ...objs) { +function jsonEditFetchResponseFn(trusted, jsonq = '', ...varargs) { 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; } + 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')}`); } - if ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); } - } - return matched; + 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; + }); + }); } -function modifyXhrResponseFn( - propsToMatch = '', - modifierFn = '' -) { - if ( typeof propsToMatch !== 'string' ) { return; } +function jsonEditFetchResponse(jsonq = '', ...args) { + jsonEditFetchResponseFn(false, jsonq, ...args); +} +function trustedJsonEditFetchResponse(jsonq = '', ...args) { + jsonEditFetchResponseFn(true, jsonq, ...args); +} +function jsonEditFetchRequestFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); - if ( modifyXhrResponseFn.xhrInstances === undefined ) { - modifyXhrResponseFn.xhrInstances = new WeakMap(); + 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 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); + 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')}`); } - return super.open(method, url, ...args); } - get response() { - const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); - return modifierFn - ? modifierFn(this, super.response) - : super.response; + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `After edit:\n${bodyAfter}`); } - get responseText() { - const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); - return modifierFn - ? modifierFn(this, super.responseText) - : super.responseText; + options.body = bodyAfter; + return context.reflect(); + }; + proxyApplyFn('fetch', proxyHandler); + proxyApplyFn('Request', proxyHandler); +} +function jsonEditFetchRequest(jsonq = '', ...args) { + jsonEditFetchRequestFn(false, jsonq, ...args); +} +function trustedJsonEditFetchRequest(jsonq = '', ...args) { + jsonEditFetchRequestFn(true, jsonq, ...args); +} +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; } - get responseXML() { - const modifierFn = modifyXhrResponseFn.xhrInstances.get(this); - return modifierFn - ? modifierFn(this, super.responseXML) - : super.responseXML; + const jsonBefore = match[1]; + let obj; + try { obj = safe.JSON_parse(jsonBefore); } catch { } + if ( typeof obj !== 'object' || obj === null ) { + linesAfter.push(lineBefore); + continue; } - }; - proxyToStringFn(TrappedXMLHttpRequest.prototype.open, NativeXMLHttpRequest.prototype.open); - proxyToStringFn(TrappedXMLHttpRequest, NativeXMLHttpRequest); - self.XMLHttpRequest = TrappedXMLHttpRequest; + 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); } -function xmlPrune( - selector = '', - selectorCheck = '', - urlPattern = '', - ...varargs -) { - if ( typeof selector !== 'string' ) { return; } - if ( selector === '' ) { return; } +function jsonlEditXhrResponseFn(trusted, jsonq = '', ...varargs) { const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('xml-prune', selector, selectorCheck, urlPattern); - const reUrl = safe.patternToRegex(urlPattern); + 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 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); + 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); } - return out; - }; - const pruneFromDoc = xmlDoc => { - try { - if ( selectorCheck !== '' && xmlDoc.querySelector(selectorCheck) === null ) { - return xmlDoc; + get response() { + const innerResponse = super.response; + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails === undefined ) { + return innerResponse; } - if ( extraArgs.logdoc ) { - const serializer = new XMLSerializer(); - safe.uboLog(logPrefix, `Document is\n\t${serializer.serializeToString(xmlDoc)}`); + const responseLength = typeof innerResponse === 'string' + ? innerResponse.length + : undefined; + if ( xhrDetails.lastResponseLength !== responseLength ) { + xhrDetails.response = undefined; + xhrDetails.lastResponseLength = responseLength; } - 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`); + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; } - } catch(ex) { - safe.uboErr(logPrefix, `Error: ${ex}`); - } - return xmlDoc; - }; - const pruneFromText = text => { - if ( (/^\s*\s*$/.test(text)) === false ) { - return text; + 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); } - try { - const xmlParser = new DOMParser(); - const xmlDoc = xmlParser.parseFromString(text, 'text/xml'); - pruneFromDoc(xmlDoc); - const serializer = new XMLSerializer(); - text = serializer.serializeToString(xmlDoc); - } catch { + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; } - 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; +} +function jsonlEditXhrResponse(jsonq = '', ...args) { + jsonlEditXhrResponseFn(false, jsonq, ...args); +} +function trustedJsonlEditXhrResponse(jsonq = '', ...args) { + jsonlEditXhrResponseFn(true, jsonq, ...args); +} +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(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 - ); - }); } + 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; + }); }); - modifyXhrResponseFn(urlPattern, (xhr, before) => { - if ( before instanceof XMLDocument ) { - return pruneFromDoc(before); +} +function jsonlEditFetchResponse(jsonq = '', ...args) { + jsonlEditFetchResponseFn(false, jsonq, ...args); +} +function trustedJsonlEditFetchResponse(jsonq = '', ...args) { + jsonlEditFetchResponseFn(true, jsonq, ...args); +} +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'; } - if ( typeof before === 'string' ) { - return pruneFromText(before); + 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; +} +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; } - return before; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { + 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); } -xmlPrune(...args); -}, -}; - - -scriptlets['m3u-prune.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; +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; + } } - 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, - }; + if ( objectPruneFn.mustProcess === undefined ) { + objectPruneFn.mustProcess = (root, needlePaths) => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; + } } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + 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'; } - 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); + } + } + if ( outcome === 'match' ) { return obj; } +} +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); } - try { - return new RegExp(match[1], match[2] || undefined); + 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; } - catch { + 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 /^/; - }, - 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; } + return found; } - 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 ( prop === '{-}' && owner instanceof Object ) { + for ( const key of Object.keys(owner) ) { + if ( objectFindOwnerFn(owner[key], next) === false ) { continue; } + delete owner[key]; + found = 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; + 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; } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; + return found; + } + if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } + owner = owner[prop]; + chain = chain.slice(pos + 1); } - return safe; } -function proxyApplyFn( - target = '', - handler = '', - options = {} +function trustedPruneInboundObject( + entryPoint = '', + argPos = '', + rawPrunePaths = '', + rawNeedlePaths = '', + ...varargs ) { + if ( entryPoint === '' ) { return; } let context = globalThis; - let prop = target; + let prop = entryPoint; for (;;) { const pos = prop.indexOf('.'); if ( pos === -1 ) { break; } @@ -41867,3612 +54831,3465 @@ function proxyApplyFn( 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); + 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; } - init(callFn, callArgs) { - this.callFn = callFn; - this.callArgs = callArgs; - return this; + } + 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; + } } - reflect() { - const r = Reflect.construct(this.callFn, this.callArgs); - this.callFn = this.callArgs = this.private = undefined; - proxyApplyFn.ctorContexts.push(this); - return r; + return Reflect.apply(target, thisArg, args); + }, + }); +} +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; + }); +} +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; + }); +} +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')}`); } - static factory(...args) { - return proxyApplyFn.ctorContexts.length !== 0 - ? proxyApplyFn.ctorContexts.pop().init(...args) - : new proxyApplyFn.CtorContext(...args); + } + 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 + }); +} +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'; + } } - }; - proxyApplyFn.applyContexts = []; - proxyApplyFn.ApplyContext = class { - constructor(...args) { - this.init(...args); + if ( outcome === 'match' ) { + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched optional "propsToMatch", "${extraArgs.propsToMatch}"`); + } + xhrInstances.set(this, xhrDetails); } - init(callFn, thisArg, callArgs) { - this.callFn = callFn; - this.thisArg = thisArg; - this.callArgs = callArgs; - return this; + return super.open(method, url, ...args); + } + get response() { + const innerResponse = super.response; + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails === undefined ) { + return innerResponse; } - 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; + const responseLength = typeof innerResponse === 'string' + ? innerResponse.length + : undefined; + if ( xhrDetails.lastResponseLength !== responseLength ) { + xhrDetails.response = undefined; + xhrDetails.lastResponseLength = responseLength; } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; } - }; - 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); + let objBefore; + if ( typeof innerResponse === 'object' ) { + objBefore = innerResponse; + } else if ( typeof innerResponse === 'string' ) { + try { + objBefore = safe.JSON_parse(innerResponse); + } catch { } - }); - proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); - Function.prototype.toString = proxiedToString; + } + 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); } - } - 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)); + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; } }; - 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 m3uPrune( - m3uPattern = '', - urlPattern = '' +function evaldataPrune( + rawPrunePaths = '', + rawNeedlePaths = '' ) { - if ( typeof m3uPattern !== 'string' ) { return; } + 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; + }); +} +function noEvalIf( + needle = '' +) { + if ( typeof needle !== '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; + 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 ( lines[i].startsWith('#EXT-X-SCTE35:') ) { - toLog.push(`\t${lines[i]}`); - lines[i] = undefined; i += 1; + if ( needle === '' || safe.logLevel > 1 ) { + safe.uboLog(logPrefix, 'Not prevented:\n', a); } - if ( lines[i].startsWith('#EXT-X-CUE-IN') ) { - toLog.push(`\t${lines[i]}`); - lines[i] = undefined; i += 1; + return context.reflect(); + }); +} +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))}`); } - if ( lines[i].startsWith('#EXT-X-SCTE35:') ) { - toLog.push(`\t${lines[i]}`); - lines[i] = undefined; i += 1; + 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 true; + return parts.join(''); }; - 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; + 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 } - return true; - }; - const pruner = text => { - if ( (/^\s*#EXTM3U/.test(text)) === false ) { return text; } - if ( m3uPattern === '' ) { - safe.uboLog(` Content:\n${text}`); - return text; + if ( matchesBoth && targetSelector !== undefined ) { + if ( elementMatches(thisArg) === false ) { return false; } } - 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]; - } + 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)); } - 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; } + } else { + h = String(callArgs[1]); } - return text; + } catch { } - 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; } + 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 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); + return context.reflect(); }; - 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 }, + const protect = owner => { + const { addEventListener } = owner; + Object.defineProperty(owner, 'addEventListener', { + set() { }, + get() { return addEventListener; } }); - if ( toLog.length !== 0 ) { - toLog.unshift(logPrefix); - safe.uboLog(toLog.join('\n')); + }; + 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); } } - 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); + if ( Object.hasOwn(window, 'addEventListener') ) { + proxyApplyFn('window.addEventListener', proxyFn); + if ( extraArgs.protect ) { protect(window); } } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { + }, extraArgs.runAt); } -m3uPrune(...args); -}, -}; - - -scriptlets['call-nothrow.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -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); +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; } } - catch { + 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(); + }); +} +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'); } - 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); - }, + 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(); + } }; - 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; } + 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; } } - lastLogType = type; - lastLogText = text; - lastLogTime = Date.now(); - return text; + if ( extraArgs.domAlert ) { + domAlert(text); + } + safe.uboLog(logPrefix, 'Prevented:\n\t', text); + return true; }; - 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; + 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; } } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; + return context.reflect(); + }, { skipToString: true }); + }; + runAt(( ) => { + self.document.addEventListener('mousemove', installTraps, { + once: true, + capture: true, + }); + }, 'interactive') } -function callNothrow( - chain = '' +function preventDialog( + selector = '', ) { - 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; - }, + 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, }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { } -callNothrow(...args); -}, -}; - - -scriptlets['remove-node-text.js'] = { -aliases: ["rmnt.js"], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...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]; +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; } - return 0; + while ( textSize < len ); + return chunks.join(' ').slice(0, len); }; - const runAt = intFromReadyState(when); - if ( intFromReadyState(document.readyState) >= runAt ) { - fn(); return; + if ( directive === 'true' ) { + return randomize(10); } - 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 safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; + if ( directive === 'emptyObj' ) { + return '{}'; } - 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 { + 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); } - 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); - }, + 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 = '', + 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' ], }; - 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; + const responseProps = { + statusText: { value: 'OK' }, }; - 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 }); + 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; } - 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; + 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); + }); } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; + return response; + }); + }); } -function getRandomTokenFn() { - const safe = safeSelf(); - return safe.String_fromCharCode(Date.now() % 26 + 97) + - safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); +function preventFetch(...args) { + preventFetchFn(false, ...args); } -function replaceNodeTextFn( - nodeName = '', - pattern = '', - replacement = '', - ...varargs +function trustedPreventFetch(...args) { + preventFetchFn(true, ...args); +} +function freezeElementProperty( + property = '', + selector = '', + pattern = '' ) { 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); - } - } + 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; } - 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 ( Object.hasOwn(HTMLElement.prototype, property) ) { + return HTMLElement.prototype; } - if ( reExcludes ) { - reExcludes.lastIndex = 0; - if ( safe.RegExp_test(reExcludes, before) ) { return; } + if ( Object.hasOwn(Element.prototype, property) ) { + return Element.prototype; } - 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()}`); + if ( Object.hasOwn(Node.prototype, property) ) { + return Node.prototype; } - safe.uboLog(logPrefix, `Text after:\n${after.trim()}`); - sedCount -= 1; + 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}`); }; - 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; + 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 ( sedCount === 0 ) { break; } + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Assigned:\n${a}`); + } + current.value = a; + }, + }); +} +function preventInnerHTML( + selector = '', + pattern = '' +) { + freezeElementProperty('innerHTML', selector, pattern); +} +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}`); + } + }); +} +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; } - 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'); + 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; } - }; - 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); } - } + if ( this.min === undefined ) { + return (n <= this.max) !== this.not; } - }; - 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'); + if ( this.max === undefined ) { + return (n >= this.min) !== this.not; + } + return (n >= this.min && n <= this.max) !== this.not; + } } -function removeNodeText( - nodeName, - includes, - ...extraArgs +function preventSetTimeout( + needleRaw = '', + delayRaw = '' ) { - replaceNodeTextFn(nodeName, '', '', 'includes', includes || '', ...extraArgs); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { + 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(); + }); } -removeNodeText(...args); -}, -}; - - -scriptlets['prevent-canvas.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -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; } +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(); } - 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; + if ( reNeedle.test(a) !== needleNot && range.test(b) ) { + callArgs[0] = function(){}; + safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`); + } + return context.reflect(); + }); +} +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(); + }); } -function preventCanvas( - contextType = '' -) { +function validateConstantFn(trusted, raw, extraArgs = {}) { 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); + 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; } } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + } else { + return; } -} catch { + 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; } -preventCanvas(...args); -}, -}; - - -scriptlets['multiup.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { - -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 }); -}; -multiup(...args); -}, -}; - - -scriptlets['break-on-call.js'] = { -aliases: [], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -function proxyApplyFn( - target = '', - handler = '', - options = {} +function setConstantFn( + trusted = false, + chain = '', + rawValue = '', + ...varargs ) { - 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); + 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; }; - proxyApplyFn.applyContexts = []; - proxyApplyFn.ApplyContext = class { - constructor(...args) { - this.init(...args); + // 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; + } } - init(callFn, thisArg, callArgs) { - this.callFn = callFn; - this.thisArg = thisArg; - this.callArgs = callArgs; - return this; + 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); } - 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; + }; + 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; } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); + 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; } - }; - 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; + 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); } - return proxyApplyFn.nativeToString.call(proxied); } }); - proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); - Function.prototype.toString = proxiedToString; + }; + trapChain(window, chain); + } + runAt(( ) => { + setConstant(chain, rawValue); + }, extraArgs.runAt); +} +function setConstant( + ...args +) { + setConstantFn(false, ...args); +} +function trustedSetConstant( + ...args +) { + 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; } - 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; } -function breakOnCall(target) { - proxyApplyFn(target, function(context) { - debugger; // eslint-disable-line no-debugger +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 = '', + 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(); }); -}; -breakOnCall(...args); -}, -}; - - -scriptlets['trusted-replace-node-text.js'] = { -aliases: ["trusted-rpnt.js","replace-node-text.js","rpnt.js"], -world: 'ISOLATED', -requiresTrust: true, -func: function (scriptletGlobals = {}, ...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]; +} +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); } - 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 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, '\\$&'); + 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; }, - 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, - }; + get(target, prop) { + if ( prop === 'toString' ) { + return target.toString.bind(target); } - return { pattern, expect }; + return Reflect.get(target, prop); }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + }); + 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')); } - 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); + if ( privatePropToValueMap.has('_recty') ) { + y = parseFloat(privatePropToValueMap.get('_recty')); } - try { - return new RegExp(match[1], match[2] || undefined); + if ( privatePropToValueMap.has('_rectw') ) { + width = parseFloat(privatePropToValueMap.get('_rectw')); + } else if ( propToValueMap.has('width') ) { + width = parseFloat(propToValueMap.get('width')); } - catch { + if ( privatePropToValueMap.has('_recth') ) { + height = parseFloat(privatePropToValueMap.get('_recth')); + } else if ( propToValueMap.has('height') ) { + height = parseFloat(propToValueMap.get('height')); } - return /^/; + return new self.DOMRect(x, y, width, height); }, - 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); + get(target, prop) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop); }, - }; - 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; } + }); +} +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 { } - 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 }); + 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; } - 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; + 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'); + }); +} +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); } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; + }); + proxyToStringFn.proxies.set(proxiedToString, proxyToStringFn.nativeToString); + Function.prototype.toString = proxiedToString; } - return safe; + proxyToStringFn.proxies.set(proxiedFn, nativeFn); } -function getRandomTokenFn() { +function modifyXhrResponseFn( + propsToMatch = '', + modifierFn = '' +) { + if ( typeof propsToMatch !== 'string' ) { return; } const safe = safeSelf(); - return safe.String_fromCharCode(Date.now() % 26 + 97) + - safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); + 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 replaceNodeTextFn( - nodeName = '', - pattern = '', - replacement = '', - ...varargs +function mpegdashPrune( + selector = '', + propsToMatch = '' ) { + if ( typeof selector !== 'string' ) { return; } + if ( selector === '' ) { return; } 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); - } - } + 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; - })(); - 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; } + }; + 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 ( reExcludes ) { - reExcludes.lastIndex = 0; - if ( safe.RegExp_test(reExcludes, before) ) { return; } + if ( match[2] ) { + const h = parseFloat(match[2].slice(0, -1)); + if ( isNaN(h) ) { return; } + seconds += h * secondsPerHour; } - 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()}`); + if ( match[3] ) { + const m = parseFloat(match[3].slice(0, -1)); + if ( isNaN(m) ) { return; } + seconds += m * secondsPerMinute; } - 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; } + if ( match[4] ) { + const s = parseFloat(match[4].slice(0, -1)); + if ( isNaN(s) ) { return; } + seconds += s; } - return count; + return seconds; }; - 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 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 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; + 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)); } - if ( sedCount === 0 ) { return stop(false); } + seconds += secondsFromPT(durAttr); } + const mpds = queryAll(xmlDoc, 'MPD[mediaPresentationDuration]'); + if ( mpds.length !== 1 ) { return; } + mpds[0].setAttribute('mediaPresentationDuration', ptFromSeconds(seconds)); + } catch { } }; - 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'); + 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; + }); } -function replaceNodeText( - nodeName, - pattern, - replacement, - ...extraArgs +function getCookieFn( + name = '' ) { - replaceNodeTextFn(nodeName, pattern, replacement, ...extraArgs); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + 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(); } -} catch { } -replaceNodeText(...args); -}, -}; - +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); + } -scriptlets['trusted-replace-fetch-response.js'] = { -aliases: ["trusted-rpfr.js"], + const cookieBefore = getCookieFn(name); + if ( cookieBefore !== undefined && options.dontOverwrite ) { return; } + if ( cookieBefore === value && options.reload ) { return; } -requiresTrust: true, -func: function (scriptletGlobals = {}, ...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}`); - } + const cookieParts = [ name, '=', value ]; + if ( expires !== '' ) { + cookieParts.push('; expires=', expires); } - return matched; -} -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; + + if ( path === '' ) { path = '/'; } + else if ( path === 'none' ) { path = ''; } + if ( path !== '' && path !== '/' ) { return; } + if ( path === '/' ) { + cookieParts.push('; path=/'); } - 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; + + 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; } - }; - bc.postMessage('areyouready?'); + if ( domain ) { + cookieParts.push(`; domain=${domain}`); + } + } + cookieParts.push('; Secure'); + } else if ( /^__(Host|Secure)-/.test(name) ) { + cookieParts.push('; Secure'); + } + + try { + document.cookie = cookieParts.join(''); } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; } - return safe; + + const done = getCookieFn(name) === value; + if ( done && options.reload ) { + window.location.reload(); + } + + return done; } -function collateFetchArgumentsFn(resource, options) { - const safe = safeSelf(); - const props = [ - 'body', 'cache', 'credentials', 'duplex', 'headers', - 'integrity', 'keepalive', 'method', 'mode', 'priority', - 'redirect', 'referrer', 'referrerPolicy', 'url' +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', ]; - const out = {}; - if ( collateFetchArgumentsFn.collateKnownProps === undefined ) { - collateFetchArgumentsFn.collateKnownProps = (src, out) => { - for ( const prop of props ) { - if ( src[prop] === undefined ) { continue; } - out[prop] = src[prop]; - } - }; +} +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; } } - 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 { + + const done = setCookieFn( + false, + name, + value, + '', + path, + safe.parseVarargs(varargs) + ); + + if ( done ) { + safe.uboLog(logPrefix, 'Done'); + } +} +function setCookieReload(name, value, path, ...args) { + setCookie(name, value, path, 'reload', '1', ...args); +} +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)); } - collateFetchArgumentsFn.collateKnownProps(clone || resource, out); + expires = time.toUTCString(); } - if ( typeof options === 'object' && options !== null ) { - collateFetchArgumentsFn.collateKnownProps(options, out); + + const done = setCookieFn( + true, + name, + value, + expires, + path, + safe.parseVarargs(varargs) + ); + + if ( done ) { + safe.uboLog(logPrefix, 'Done'); } - return out; } -function replaceFetchResponseFn( - trusted = false, - pattern = '', - replacement = '', - propsToMatch = '', +function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) { + trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args); +} +function removeCookie( + needle = '', ...varargs ) { - if ( trusted !== true ) { return; } + if ( typeof needle !== 'string' ) { 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 reName = safe.patternToRegex(needle); 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')}`); + 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', '')}`; } } - 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); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + 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 }); } -} catch { } -trustedReplaceFetchResponse(...args); -}, -}; +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 = '{}'; + } -scriptlets['trusted-replace-xhr-response.js'] = { -aliases: [], + const trustedValues = [ + '', + 'undefined', 'null', + '{}', '[]', '""', + '$remove$', + ...getSafeCookieValuesFn(), + ]; -requiresTrust: true, -func: function (scriptletGlobals = {}, ...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 ( trusted ) { + if ( value.includes('$now$') ) { + value = value.replaceAll('$now$', Date.now()); } - if ( pattern !== undefined ) { - needles.set(prop, safe.initPattern(pattern, options)); - } else if ( implicit !== '' ) { - needles.set(implicit, safe.initPattern(prop, options)); + if ( value.includes('$currentDate$') ) { + value = value.replaceAll('$currentDate$', `${Date()}`); } - } - return needles; -} -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; } + if ( value.includes('$currentISODate$') ) { + value = value.replaceAll('$currentISODate$', (new Date()).toISOString()); } - lastLogType = type; - lastLogText = text; - lastLogTime = Date.now(); - return text; - }; + } 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 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 }); + 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); } - 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; + } else { + + const before = storage.getItem(key); + const after = `${value}`; + modified = after !== before; + if ( modified ) { + storage.setItem(key, after); } - }; - bc.postMessage('areyouready?'); + } } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; } - return safe; + + if ( modified && typeof options.reload === 'number' ) { + setTimeout(( ) => { window.location.reload(); }, options.reload); + } } -function matchObjectPropertiesFn(propNeedles, ...objs) { +function setLocalStorageItem(key = '', value = '', ...varargs) { 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; + const options = safe.parseVarargs(varargs) + setLocalStorageItemFn('local', false, key, value, options); } -function trustedReplaceXhrResponse( - pattern = '', - replacement = '', - propsToMatch = '', - ...varargs +function setSessionStorageItem(key = '', value = '', ...varargs) { + const safe = safeSelf(); + const options = safe.parseVarargs(varargs) + setLocalStorageItemFn('session', false, key, value, options); +} +function trustedSetLocalStorageItem(key = '', value = '', ...varargs) { + const safe = safeSelf(); + const options = safe.parseVarargs(varargs) + setLocalStorageItemFn('local', true, key, value, options); +} +function trustedSetSessionStorageItem(key = '', value = '', ...varargs) { + const safe = safeSelf(); + const options = safe.parseVarargs(varargs) + setLocalStorageItemFn('session', true, key, value, options); +} +function abortOnPropertyRead( + chain = '' ) { + if ( typeof chain !== 'string' ) { return; } + if ( chain === '' ) { return; } 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); + 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 super.open(method, url, ...args); + return; } - 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); + const prop = chain.slice(0, pos); + let v = owner[prop]; + chain = chain.slice(pos + 1); + if ( v ) { + makeProxy(v, chain); + return; } - get responseText() { - const response = this.response; - if ( typeof response !== 'string' ) { - return super.responseText; + 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); + } } - return response; - } + }); }; -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { + const owner = window; + makeProxy(owner, chain); } -trustedReplaceXhrResponse(...args); -}, -}; - - -scriptlets['trusted-click-element.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -function runAtHtmlElementFn(fn) { - if ( document.documentElement ) { - fn(); - return; +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); } - const observer = new MutationObserver(( ) => { - observer.disconnect(); - fn(); + delete owner[prop]; + Object.defineProperty(owner, prop, { + set: function() { + safe.uboLog(logPrefix, 'Aborted'); + throw new ReferenceError(exceptionToken); + } }); - observer.observe(document, { childList: true }); -} -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 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); - } +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 out; + return target.apply(thisArg, args); } - 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 }); + }); +} +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; } - }; - } - if ( until === 0 ) { - return lookupElementsFn.lookup(directive); - } - return new Promise(resolve => { - lookupElementsFn.lookupAsync({ directive, until, resolve }); + return target.apply(thisArg, args); + } }); } -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 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 }); } -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; +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 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 { + 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)'); } - 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; } + } catch { } - lastLogType = type; - lastLogText = text; - lastLogTime = Date.now(); - return text; + if ( mustStay ) { return; } + if ( document.readyState !== 'complete' ) { return; } + observer.disconnect(); }; - 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 }); + 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; } } - 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; + for ( let j = 0; j < removedNodes.length && skip; j++ ) { + if ( removedNodes[j].nodeType === 1 ) { skip = false; 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; -} -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); + } + 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'); } -function trustedClickElement( - selectors = '', - extraMatch = '', - delay = '' +function webrtcIf( + good = '' ) { + if ( typeof good !== 'string' ) { return; } 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 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; } + } } - 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; } + if ( typeof server.username === 'string' ) { + if ( reGood.test(server.username) ) { 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; + if ( typeof server.credential === 'string' ) { + if ( reGood.test(server.credential) ) { return true; } } } - } - - 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); - }); + neuteredPeerConnections.add(instance); + return false; }; - - 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 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); + } }); +} +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 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; + 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(', ')})`); } - if ( step.startsWith('!') ) { continue; } - const clicked = await waitForElement(step); - if ( clicked ) { continue; } - safe.uboLog(logPrefix, `Timed out waiting on ${step}`); - break; + return context.reflect(); } - }; - - runAtHtmlElementFn(process); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); + 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; + }); +} +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); } -} catch { } -trustedClickElement(...args); -}, -}; - - -scriptlets['trusted-replace-outbound-text.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; +function windowNameDefuser() { + if ( window === window.top ) { + window.name = ''; } - 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); +} +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; } - catch { + 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; + } } - return /^/; + 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(); + } +} +function alertBuster() { + window.alert = new Proxy(window.alert, { + apply: function(a) { + console.info(a); }, - 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); + get(target, prop) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop); }, + }); +} +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); }; - 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; } + const noop = function() { + }; + pc.prototype = { + close: noop, + createDataChannel: noop, + createOffer: noop, + setRemoteDescription: noop, + toString: function() { + return '[object RTCPeerConnection]'; } - 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; + var z = window[rtcName]; + window[rtcName] = pc.bind(window); + if ( z.prototype ) { + z.prototype.createDataChannel = function() { + return { + close: function() {}, + send: function() {} + }; + }.bind(null); + } +} +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; } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; + target = target.parentNode; + } + }, { capture: true }); } -function proxyApplyFn( - target = '', - handler = '', - options = {} +function xmlPrune( + selector = '', + selectorCheck = '', + urlPattern = '', + ...varargs ) { - 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); + 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; } - init(callFn, thisArg, callArgs) { - this.callFn = callFn; - this.thisArg = thisArg; - this.callArgs = callArgs; - return this; + if ( extraArgs.logdoc ) { + const serializer = new XMLSerializer(); + safe.uboLog(logPrefix, `Document is\n\t${serializer.serializeToString(xmlDoc)}`); } - 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; + 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`); } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); + } 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; } - }; - 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); - } + 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 + ); }); - 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)); + }); + modifyXhrResponseFn(urlPattern, (xhr, before) => { + if ( before instanceof XMLDocument ) { + return pruneFromDoc(before); } - }; - 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; + if ( typeof before === 'string' ) { + return pruneFromText(before); + } + return before; + }); } -function trustedReplaceOutboundText( - propChain = '', - rawPattern = '', - rawReplacement = '', - ...varargs +function m3uPrune( + m3uPattern = '', + urlPattern = '' ) { - if ( propChain === '' ) { return; } + if ( typeof m3uPattern !== 'string' ) { 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; } + 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); } - if ( rawPattern === '' ) { - safe.uboLog(logPrefix, 'Decoded outbound text:\n', textBefore); - return encodedTextBefore; + 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; } - 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); + 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; } - let encodedTextAfter = textAfter; - if ( extraArgs.encoding === 'base64' ) { - encodedTextAfter = self.btoa(textAfter); + 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); } - return encodedTextAfter; }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { } -trustedReplaceOutboundText(...args); -}, -}; - - -scriptlets['trusted-suppress-native-method.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -function proxyApplyFn( - target = '', - handler = '', - options = {} +function callNothrow( + chain = '' ) { - let context = globalThis; - let prop = target; + if ( typeof chain !== 'string' ) { return; } + if ( chain === '' ) { return; } + const safe = safeSelf(); + const parts = safe.String_split.call(chain, '.'); + let owner = window, prop; 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); + prop = parts.shift(); + if ( parts.length === 0 ) { break; } + owner = owner[prop]; + if ( owner instanceof Object === false ) { return; } } - const fn = context[prop]; + if ( prop === '' ) { return; } + const fn = owner[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); + owner[prop] = new Proxy(fn, { + apply: function(...args) { + let r; + try { + r = Reflect.apply(...args); + } catch { } - }; - 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); + return r; + }, + }); +} +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); } - }); - 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)); + 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; }; - 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 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 { + 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; } - 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); - }, + if ( sedCount === 0 ) { break; } + } + return count; }; - 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; } + 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'); } - 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; + 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); } } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; + } + }; + 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 getRandomTokenFn() { +function removeNodeText( + nodeName, + includes, + ...extraArgs +) { + replaceNodeTextFn(nodeName, '', '', 'includes', includes || '', ...extraArgs); +} +function preventCanvas( + contextType = '' +) { const safe = safeSelf(); - return safe.String_fromCharCode(Date.now() % 26 + 97) + - safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); + 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); + } + }); } -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); +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 }); +} +function breakOnCall(target) { + proxyApplyFn(target, function(context) { + debugger; // eslint-disable-line no-debugger + return context.reflect(); + }); +} +function replaceNodeText( + nodeName, + pattern, + replacement, + ...extraArgs +) { + 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; + }); } - }.bind(); - return token; + }); } -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; +function trustedReplaceFetchResponse(...args) { + replaceFetchResponseFn(true, ...args); } -function trustedSuppressNativeMethod( - methodPath = '', - signature = '', - how = '', - stack = '' +function trustedReplaceXhrResponse( + pattern = '', + replacement = '', + propsToMatch = '', + ...varargs ) { - 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(); + 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 ( signatureArg.type === 'pattern' ) { - if ( safe.RegExp_test(signatureArg.re, targetArg) === false ) { - return context.reflect(); + if ( outcome === 'match' ) { + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched "propsToMatch"`); } + xhrInstances.set(outerXhr, xhrDetails); } + return super.open(method, url, ...args); } - 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(); - } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedSuppressNativeMethod(...args); -}, -}; - - -scriptlets['trusted-prevent-dom-bypass.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -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); + get response() { + const innerResponse = super.response; + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails === undefined ) { + return innerResponse; } - const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); - if ( match !== null ) { - return { - re: new this.RegExp( - match[1], - match[2] || options.flags - ), - expect, - }; + const responseLength = typeof innerResponse === 'string' + ? innerResponse.length + : undefined; + if ( xhrDetails.lastResponseLength !== responseLength ) { + xhrDetails.response = undefined; + xhrDetails.lastResponseLength = responseLength; } - if ( options.flags !== undefined ) { - return { - re: new this.RegExp(this.escapeRegexChars(pattern), - options.flags - ), - expect, - }; + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; } - return { pattern, expect }; - }, - testPattern(details, haystack) { - if ( details.matchAll ) { return true; } - if ( details.re ) { - return this.RegExp_test(details.re, haystack) === details.expect; + if ( typeof innerResponse !== 'string' ) { + return (xhrDetails.response = innerResponse); } - 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); + if ( reIncludes && reIncludes.test(innerResponse) === false ) { + return (xhrDetails.response = innerResponse); } - try { - return new RegExp(match[1], match[2] || undefined); + const textBefore = innerResponse; + const textAfter = textBefore.replace(rePattern, replacement); + if ( textAfter !== textBefore ) { + safe.uboLog(logPrefix, 'Match'); } - catch { + return (xhrDetails.response = textAfter); + } + get responseText() { + const response = this.response; + if ( typeof response !== 'string' ) { + return super.responseText; } - 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; } + return response; } - 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 }); +} +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 = '', + 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; } - bcBuffer.push({ type, text }); + 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; }; - 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; + for ( const { not, type, re } of assertions ) { + switch ( type ) { + case 'cookie': + if ( hasNeedle(allCookies, re) === not ) { return; } break; - case 'setScriptletLogLevelToTwo': - safe.logLevel = 2; + case 'localStorage': + if ( hasNeedle(allStorageItems, re) === not ) { return; } 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; + + 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); +} +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; + }); } -function proxyApplyFn( - target = '', - handler = '', - options = {} +function trustedSuppressNativeMethod( + methodPath = '', + signature = '', + how = '', + stack = '' ) { - 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; + 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(); + } } - 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; + if ( signatureArg.type === 'pattern' ) { + if ( safe.RegExp_test(signatureArg.re, targetArg) === false ) { + return context.reflect(); + } } - static factory(...args) { - return proxyApplyFn.applyContexts.length !== 0 - ? proxyApplyFn.applyContexts.pop().init(...args) - : new proxyApplyFn.ApplyContext(...args); + } + if ( stackNeedle.matchAll !== true ) { + const logLevel = safe.logLevel > 1 ? 'all' : ''; + if ( matchesStackTraceFn(stackNeedle, logLevel) === false ) { + return context.reflect(); } - }; - 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 ( how === 'debug' ) { + debugger; // eslint-disable-line no-debugger + return context.reflect(); } - }; - 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; + safe.uboLog(logPrefix, `Suppressed:\n${callArgs.join('\n')}`); + if ( how === 'abort' ) { + throw new ReferenceError(); + } + }); } function trustedPreventDomBypass( methodPath = '', @@ -45513,347 +58330,6 @@ function trustedPreventDomBypass( } return r; }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedPreventDomBypass(...args); -}, -}; - - -scriptlets['trusted-override-element-method.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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 trustedOverrideElementMethod( methodPath = '', @@ -45887,19 +58363,13 @@ function trustedOverrideElementMethod( } return validateConstantFn(true, disposition, extraArgs); }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { } -trustedOverrideElementMethod(...args); -}, -}; - +const table = { abortCurrentScript, setAttr, trustedSetAttr, removeAttr, trustedCreateHTML, hrefSanitizer, proxyApplyConfig, editOutboundObject, trustedEditOutboundObject, jsonEdit, trustedJsonEdit, editInboundObject, trustedEditInboundObject, editThisObject, trustedEditThisObject, editObjectOnGetter, trustedEditObjectOnGetter, editObjectOnSetter, trustedEditObjectOnSetter, editElementObject, trustedEditElementObject, jsonEditXhrResponse, trustedJsonEditXhrResponse, jsonEditXhrRequest, trustedJsonEditXhrRequest, jsonEditFetchResponse, trustedJsonEditFetchResponse, jsonEditFetchRequest, trustedJsonEditFetchRequest, jsonlEditXhrResponse, trustedJsonlEditXhrResponse, jsonlEditFetchResponse, trustedJsonlEditFetchResponse, abortOnStackTrace, trustedPruneInboundObject, trustedPruneOutboundObject, jsonPrune, jsonPruneFetchResponse, jsonPruneXhrResponse, evaldataPrune, noEvalIf, preventAddEventListener, preventBab, preventClipboardWrite, preventDialog, preventFetch, trustedPreventFetch, freezeElementProperty, preventInnerHTML, preventNavigation, preventSetTimeout, preventSetInterval, preventRequestAnimationFrame, setConstant, trustedSetConstant, trustedReplaceArgument, spoofCSS, preventXhr, trustedPreventXhr, mpegdashPrune, setCookie, setCookieReload, trustedSetCookie, trustedSetCookieReload, removeCookie, setLocalStorageItem, setSessionStorageItem, trustedSetLocalStorageItem, trustedSetSessionStorageItem, abortOnPropertyRead, abortOnPropertyWrite, adjustSetInterval, adjustSetTimeout, preventRefresh, removeClass, webrtcIf, noWindowOpenIf, closeWindow, windowNameDefuser, overlayBuster, alertBuster, noWebrtc, disableNewtabLinks, xmlPrune, m3uPrune, callNothrow, removeNodeText, preventCanvas, multiup, breakOnCall, replaceNodeText, trustedReplaceFetchResponse, trustedReplaceXhrResponse, trustedClickElement, trustedReplaceOutboundText, trustedSuppressNativeMethod, trustedPreventDomBypass, trustedOverrideElementMethod }; +for (const [name, ...args] of calls) { + try { table[name](...args); } catch {} +} +} +export { functions }; export default scriptlets; From fd3ab2233bd7e2ca1b1a183f3a8eca3989a52416 Mon Sep 17 00:00:00 2001 From: Krzysztof Modras Date: Tue, 6 Oct 2026 13:10:59 +0200 Subject: [PATCH 2/3] Drop self-contained scriptlet wrappers and the shared safeSelf global --- build.ts | 18 - test.js | 41 +- ubo.js | 46936 +---------------------------------------------------- 3 files changed, 884 insertions(+), 46111 deletions(-) diff --git a/build.ts b/build.ts index 6cce4d7..a010a08 100644 --- a/build.ts +++ b/build.ts @@ -18,19 +18,6 @@ for (const scriptlet of scriptlets) { } } -// uBO runs all scriptlets of a page in one scope, so they share one safeSelf() cache. Our scriptlets are separate functions, so they share it on globalThis. -// Else each hook captures the earlier hooks as natives, and calls grow as 2^n. The uBO version is in the key, so other versions do not share it. -const safeSelf = index.get('safe-self.fn').fn; -const shareSafeSelf = `try { - const key = Symbol.for('safeSelf.${tagName}'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -`; - // Each function once, by name, so compose() and run() can declare them in one shared scope. const functions = new Map(); const entryFunctions = []; @@ -64,11 +51,6 @@ ${scriptlet.world ? `world: '${scriptlet.world}',` : '' } requiresTrust: ${scriptlet.requiresTrust || false}, fn: '${scriptlet.fn.name}', dependencies: ${JSON.stringify(deps.map((dep) => dep.name))}, -func: function (scriptletGlobals = {}, ...args) { -${deps.map((dep) => dep.toString()).join('\n')} -${scriptlet.fn.toString()}; -${deps.includes(safeSelf) ? shareSafeSelf : ''}${scriptlet.fn.name}(...args); -}, }; `; }) diff --git a/test.js b/test.js index d8a6217..f8bba25 100644 --- a/test.js +++ b/test.js @@ -2,6 +2,7 @@ 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"; test("default export is an object", () => { assert(typeof scriptlets === "object"); @@ -11,8 +12,12 @@ test("each scriptlet has basic properties", () => { for (const [name, scriptlet] of Object.entries(scriptlets)) { assert(name.length > 0, `${name} - name is too short`); assert( - scriptlet.func instanceof Function, - `${name} - func is not have a Function` + 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` ); assert( scriptlet.aliases instanceof Array, @@ -31,13 +36,8 @@ suite("uBO", () => { suite("safeSelf() cache", () => { // Each hook clones the argument with safe.JSON_parse(safe.JSON_stringify(obj)) const scriptlet = scriptlets["trusted-edit-inbound-object.js"]; - const func = scriptlet.func; const HOOKS = 6; const hookArgs = (i) => ["JSON.stringify", "0", `[?.hook${i}]+={"edited${i}":true}`]; - // Same code as the extension makes for each scriptlet; the first argument is scriptletGlobals - const hooks = Array.from({ length: HOOKS }, (_, i) => - `(${func})(...${JSON.stringify([{}, ...hookArgs(i)])});` - ); // A fresh realm is the page; counters wrap the native JSON methods before the scriptlets run function createPage() { @@ -64,30 +64,13 @@ suite("safeSelf() cache", () => { assert.deepStrictEqual({ ...page.calls }, { parse: HOOKS, stringify: HOOKS + 1 }); } - function safeSelfSymbols(page) { - return vm + function assertNoGlobal(page) { + const keys = vm .runInContext("Object.getOwnPropertySymbols(globalThis)", page) .filter((key) => key.description.startsWith("safeSelf.")); + assert.strictEqual(keys.length, 0); } - test("is shared in a realm, so stacked hooks stay linear", () => { - const page = createPage(); - for (const hook of hooks) { - vm.runInContext(hook, page); - } - assertLinear(page); - }); - - test("is read-only and not enumerable", () => { - const page = createPage(); - vm.runInContext(hooks.join("\n"), page); - - const keys = safeSelfSymbols(page); - assert.strictEqual(keys.length, 1); - const { value, ...flags } = Object.getOwnPropertyDescriptor(page, keys[0]); - assert.deepStrictEqual(flags, { writable: false, enumerable: false, configurable: false }); - }); - test("compose() shares it in one scope, without a global", () => { const page = createPage(); const code = compose( @@ -97,7 +80,7 @@ suite("safeSelf() cache", () => { vm.runInContext(code, page); assertLinear(page); - assert.strictEqual(safeSelfSymbols(page).length, 0); + assertNoGlobal(page); }); test("run() shares it in one scope, without a global", () => { @@ -107,6 +90,6 @@ suite("safeSelf() cache", () => { vm.runInContext(`(${run})(...${JSON.stringify([{}, calls])});`, page); assertLinear(page); - assert.strictEqual(safeSelfSymbols(page).length, 0); + assertNoGlobal(page); }); }); diff --git a/ubo.js b/ubo.js index cc4f57c..8369289 100644 --- a/ubo.js +++ b/ubo.js @@ -6162,46071 +6162,879 @@ aliases: ["abort-current-inline-script.js","acis.js","acs.js"], requiresTrust: false, fn: 'abortCurrentScript', dependencies: ["runAtHtmlElementFn","trapPropertyFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","abortCurrentScriptFn"], -func: function (scriptletGlobals = {}, ...args) { -function runAtHtmlElementFn(fn) { - if ( document.documentElement ) { - fn(); - return; - } - const observer = new MutationObserver(( ) => { - observer.disconnect(); - fn(); - }); - observer.observe(document, { childList: true }); -} -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; -} -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; -} -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 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 }); -} -function abortCurrentScript(...args) { - runAtHtmlElementFn(( ) => { - abortCurrentScriptFn(...args); - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -abortCurrentScript(...args); -}, -}; - - -scriptlets['set-attr.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -fn: 'setAttr', -dependencies: ["runAt","setAttrFn","safeSelf"], -func: function (scriptletGlobals = {}, ...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, - 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'); -} -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; -} -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); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -setAttr(...args); -}, -}; - - -scriptlets['trusted-set-attr.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -fn: 'trustedSetAttr', -dependencies: ["runAt","setAttrFn","safeSelf"], -func: function (scriptletGlobals = {}, ...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, - 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'); -} -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; -} -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); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedSetAttr(...args); -}, -}; - - -scriptlets['remove-attr.js'] = { -aliases: ["ra.js"], - -requiresTrust: false, -fn: 'removeAttr', -dependencies: ["safeSelf","runAt","onIdleFn","offIdleFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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 = '', - 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+/)); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -removeAttr(...args); -}, -}; - - -scriptlets['trusted-create-html.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -fn: 'trustedCreateHTML', -dependencies: ["safeSelf","runAt"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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'); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedCreateHTML(...args); -}, -}; - - -scriptlets['href-sanitizer.js'] = { -aliases: ["urlskip.js"], -world: 'ISOLATED', -requiresTrust: false, -fn: 'hrefSanitizer', -dependencies: ["urlSkip","safeSelf","runAt","onIdleFn"], -func: function (scriptletGlobals = {}, ...args) { -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 { - } -} -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; -} -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 onIdleFn(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); -} -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'); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -hrefSanitizer(...args); -}, -}; - - -scriptlets['proxy-apply-config.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'proxyApplyConfig', -dependencies: [], -func: function (scriptletGlobals = {}, ...args) { - -function proxyApplyConfig(config = '') { - try { - if ( typeof proxyApplyFn !== 'function' ) { return; } - config = JSON.parse(config); - if ( typeof config !== 'object' ) { return; } - Object.assign(proxyApplyFn, config); - } catch { - } -}; -proxyApplyConfig(...args); -}, -}; - - -scriptlets['edit-outbound-object.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'editOutboundObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editOutboundObjectFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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; - }); -} -function editOutboundObject(propChain = '', jsonq = '') { - editOutboundObjectFn(false, propChain, jsonq); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -editOutboundObject(...args); -}, -}; - - -scriptlets['trusted-edit-outbound-object.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedEditOutboundObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editOutboundObjectFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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; - }); -} -function trustedEditOutboundObject(propChain = '', jsonq = '') { - editOutboundObjectFn(true, propChain, jsonq); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedEditOutboundObject(...args); -}, -}; - - -scriptlets['json-edit.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonEdit', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","jsonEditFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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; - }); -} -function jsonEdit(jsonq = '', ...varargs) { - jsonEditFn(false, jsonq, ...varargs); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonEdit(...args); -}, -}; - - -scriptlets['trusted-json-edit.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedJsonEdit', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","jsonEditFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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; - }); -} -function trustedJsonEdit(jsonq = '', ...varargs) { - jsonEditFn(true, jsonq, ...varargs); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonEdit(...args); -}, -}; - - -scriptlets['edit-inbound-object.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'editInboundObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editInboundObjectFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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(); - }); -} -function editInboundObject(propChain = '', argPos = '', jsonq = '') { - editInboundObjectFn(false, propChain, argPos, jsonq); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -editInboundObject(...args); -}, -}; - - -scriptlets['trusted-edit-inbound-object.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedEditInboundObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editInboundObjectFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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(); - }); -} -function trustedEditInboundObject(propChain = '', argPos = '', jsonq = '') { - editInboundObjectFn(true, propChain, argPos, jsonq); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedEditInboundObject(...args); -}, -}; - - -scriptlets['edit-this-object.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'editThisObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editThisObjectFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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; - }); -} -function editThisObject(...args) { - editThisObjectFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -editThisObject(...args); -}, -}; - - -scriptlets['trusted-edit-this-object.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedEditThisObject', -dependencies: ["safeSelf","proxyApplyFn","JSONPath","editThisObjectFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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; - }); -} -function trustedEditThisObject(...args) { - editThisObjectFn(true, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedEditThisObject(...args); -}, -}; - - -scriptlets['edit-object-on-getter.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'editObjectOnGetter', -dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnGetterFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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; - } - }); -} -function editObjectOnGetter(...args) { - editObjectOnGetterFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -editObjectOnGetter(...args); -}, -}; - - -scriptlets['trusted-edit-object-on-getter.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedEditObjectOnGetter', -dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnGetterFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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; - } - }); -} -function trustedEditObjectOnGetter(...args) { - editObjectOnGetterFn(true, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedEditObjectOnGetter(...args); -}, -}; - - -scriptlets['edit-object-on-setter.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'editObjectOnSetter', -dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnSetterFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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); - } -} -function editObjectOnSetter(...args) { - editObjectOnSetterFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -editObjectOnSetter(...args); -}, -}; - - -scriptlets['trusted-edit-object-on-setter.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedEditObjectOnSetter', -dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnSetterFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; - } - } -} -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); - } -} -function trustedEditObjectOnSetter(...args) { - editObjectOnSetterFn(true, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedEditObjectOnSetter(...args); -}, -}; - - -scriptlets['edit-element-object.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'editElementObject', -dependencies: ["sleepFn","safeSelf","onIdleFn","offIdleFn","lookupElementsFn","JSONPath","editElementObjectFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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 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 }); - }); -} -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; - } - } -} -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 ); -} -function editElementObject(...args) { - editElementObjectFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -editElementObject(...args); -}, -}; - - -scriptlets['trusted-edit-element-object.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedEditElementObject', -dependencies: ["sleepFn","safeSelf","onIdleFn","offIdleFn","lookupElementsFn","JSONPath","editElementObjectFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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 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 }); - }); -} -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; - } - } -} -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 ); -} -function trustedEditElementObject(...args) { - editElementObjectFn(true, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedEditElementObject(...args); -}, -}; - - -scriptlets['json-edit-xhr-response.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonEditXhrResponse', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrResponseFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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; -} -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; - } - } -} -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; - } - }; -} -function jsonEditXhrResponse(jsonq = '', ...args) { - jsonEditXhrResponseFn(false, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonEditXhrResponse(...args); -}, -}; - - -scriptlets['trusted-json-edit-xhr-response.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedJsonEditXhrResponse', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrResponseFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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; -} -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; - } - } -} -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; - } - }; -} -function trustedJsonEditXhrResponse(jsonq = '', ...args) { - jsonEditXhrResponseFn(true, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonEditXhrResponse(...args); -}, -}; - - -scriptlets['json-edit-xhr-request.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonEditXhrRequest', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrRequestFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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; -} -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; - } - } -} -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; - } - }; -} -function jsonEditXhrRequest(jsonq = '', ...args) { - jsonEditXhrRequestFn(false, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonEditXhrRequest(...args); -}, -}; - - -scriptlets['trusted-json-edit-xhr-request.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedJsonEditXhrRequest', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrRequestFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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; -} -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; - } - } -} -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; - } - }; -} -function trustedJsonEditXhrRequest(jsonq = '', ...args) { - jsonEditXhrRequestFn(true, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonEditXhrRequest(...args); -}, -}; - - -scriptlets['json-edit-fetch-response.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonEditFetchResponse', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchResponseFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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; -} -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; - } - } -} -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; -} -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( - `${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; - }); - }); -} -function jsonEditFetchResponse(jsonq = '', ...args) { - jsonEditFetchResponseFn(false, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonEditFetchResponse(...args); -}, -}; - - -scriptlets['trusted-json-edit-fetch-response.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedJsonEditFetchResponse', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchResponseFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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; -} -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; - } - } -} -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; -} -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( - `${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; - }); - }); -} -function trustedJsonEditFetchResponse(jsonq = '', ...args) { - jsonEditFetchResponseFn(true, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonEditFetchResponse(...args); -}, -}; - - -scriptlets['json-edit-fetch-request.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonEditFetchRequest', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchRequestFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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; -} -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; - } - } -} -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; -} -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 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); -} -function jsonEditFetchRequest(jsonq = '', ...args) { - jsonEditFetchRequestFn(false, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonEditFetchRequest(...args); -}, -}; - - -scriptlets['trusted-json-edit-fetch-request.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedJsonEditFetchRequest', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchRequestFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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; -} -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; - } - } -} -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; -} -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 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); -} -function trustedJsonEditFetchRequest(jsonq = '', ...args) { - jsonEditFetchRequestFn(true, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonEditFetchRequest(...args); -}, -}; - - -scriptlets['jsonl-edit-xhr-response.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonlEditXhrResponse', -dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","jsonlEditFn","JSONPath","jsonlEditXhrResponseFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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); -} -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; - } - } -} -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; - } - }; -} -function jsonlEditXhrResponse(jsonq = '', ...args) { - jsonlEditXhrResponseFn(false, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonlEditXhrResponse(...args); -}, -}; - - -scriptlets['trusted-jsonl-edit-xhr-response.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedJsonlEditXhrResponse', -dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","jsonlEditFn","JSONPath","jsonlEditXhrResponseFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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); -} -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; - } - } -} -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; - } - }; -} -function trustedJsonlEditXhrResponse(jsonq = '', ...args) { - jsonlEditXhrResponseFn(true, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonlEditXhrResponse(...args); -}, -}; - - -scriptlets['jsonl-edit-fetch-response.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonlEditFetchResponse', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","jsonlEditFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonlEditFetchResponseFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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 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); -} -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; - } - } -} -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; -} -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 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; - }); - }); -} -function jsonlEditFetchResponse(jsonq = '', ...args) { - jsonlEditFetchResponseFn(false, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonlEditFetchResponse(...args); -}, -}; - - -scriptlets['trusted-jsonl-edit-fetch-response.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedJsonlEditFetchResponse', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","jsonlEditFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonlEditFetchResponseFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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 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); -} -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; - } - } -} -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; -} -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 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; - }); - }); -} -function trustedJsonlEditFetchResponse(jsonq = '', ...args) { - jsonlEditFetchResponseFn(true, jsonq, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedJsonlEditFetchResponse(...args); -}, -}; - - -scriptlets['abort-on-stack-trace.js'] = { -aliases: ["aost.js"], - -requiresTrust: false, -fn: 'abortOnStackTrace', -dependencies: ["matchesStackTraceFn","safeSelf","getRandomTokenFn","getExceptionTokenFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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 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); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -abortOnStackTrace(...args); -}, -}; - - -scriptlets['trusted-prune-inbound-object.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedPruneInboundObject', -dependencies: ["getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","safeSelf","objectFindOwnerFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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; -} -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 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; -} -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); - } -} -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); - }, - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedPruneInboundObject(...args); -}, -}; - - -scriptlets['trusted-prune-outbound-object.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedPruneOutboundObject', -dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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); - } -} -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; -} -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 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; -} -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 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedPruneOutboundObject(...args); -}, -}; - - -scriptlets['json-prune.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonPrune', -dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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); - } -} -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; -} -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 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; -} -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 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonPrune(...args); -}, -}; - - -scriptlets['json-prune-fetch-response.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonPruneFetchResponse', -dependencies: ["parsePropertiesToMatchFn","objectFindOwnerFn","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","matchObjectPropertiesFn","safeSelf","collateFetchArgumentsFn"], -func: function (scriptletGlobals = {}, ...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 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); - } -} -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 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; -} -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 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 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; -} -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 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 - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonPruneFetchResponse(...args); -}, -}; - - -scriptlets['json-prune-xhr-response.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'jsonPruneXhrResponse', -dependencies: ["parsePropertiesToMatchFn","objectFindOwnerFn","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","safeSelf","matchObjectPropertiesFn"], -func: function (scriptletGlobals = {}, ...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 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); - } -} -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 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; -} -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 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; -} -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 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; - } - }; -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -jsonPruneXhrResponse(...args); -}, -}; - - -scriptlets['evaldata-prune.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'evaldataPrune', -dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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); - } -} -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; -} -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 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; -} -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 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -evaldataPrune(...args); -}, -}; - - -scriptlets['noeval-if.js'] = { -aliases: ["prevent-eval-if.js"], - -requiresTrust: false, -fn: 'noEvalIf', -dependencies: ["safeSelf","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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(); - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -noEvalIf(...args); -}, -}; - - -scriptlets['prevent-addEventListener.js'] = { -aliases: ["addEventListener-defuser.js","aeld.js"], - -requiresTrust: false, -fn: 'preventAddEventListener', -dependencies: ["safeSelf","runAt","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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 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); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventAddEventListener(...args); -}, -}; - - -scriptlets['prevent-bab.js'] = { -aliases: ["bab-defuser.js","nobab.js"], - -requiresTrust: false, -fn: 'preventBab', -dependencies: ["safeSelf","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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(); - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventBab(...args); -}, -}; - - -scriptlets['prevent-clipboard-write.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'preventClipboardWrite', -dependencies: ["safeSelf","runAt","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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 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') -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventClipboardWrite(...args); -}, -}; - - -scriptlets['prevent-dialog.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -fn: 'preventDialog', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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, - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventDialog(...args); -}, -}; - - -scriptlets['prevent-fetch.js'] = { -aliases: ["no-fetch-if.js"], - -requiresTrust: false, -fn: 'preventFetch', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","generateContentFn","safeSelf","collateFetchArgumentsFn","preventFetchFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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 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; -} -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 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; - }); - }); -} -function preventFetch(...args) { - preventFetchFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventFetch(...args); -}, -}; - - -scriptlets['trusted-prevent-fetch.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedPreventFetch', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","generateContentFn","safeSelf","collateFetchArgumentsFn","preventFetchFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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 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; -} -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 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; - }); - }); -} -function trustedPreventFetch(...args) { - preventFetchFn(true, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedPreventFetch(...args); -}, -}; - - -scriptlets['freeze-element-property.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'freezeElementProperty', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; - }, - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -freezeElementProperty(...args); -}, -}; - - -scriptlets['prevent-innerHTML.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'preventInnerHTML', -dependencies: ["safeSelf","freezeElementProperty"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; - }, - }); -} -function preventInnerHTML( - selector = '', - pattern = '' -) { - freezeElementProperty('innerHTML', selector, pattern); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventInnerHTML(...args); -}, -}; - - -scriptlets['prevent-navigation.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -fn: 'preventNavigation', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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}`); - } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventNavigation(...args); -}, -}; - - -scriptlets['prevent-setTimeout.js'] = { -aliases: ["no-setTimeout-if.js","nostif.js","setTimeout-defuser.js"], - -requiresTrust: false, -fn: 'preventSetTimeout', -dependencies: ["safeSelf","RangeParser","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; - } -} -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 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(); - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventSetTimeout(...args); -}, -}; - - -scriptlets['prevent-setInterval.js'] = { -aliases: ["no-setInterval-if.js","nosiif.js","setInterval-defuser.js"], - -requiresTrust: false, -fn: 'preventSetInterval', -dependencies: ["safeSelf","RangeParser","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; - } -} -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 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(); - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventSetInterval(...args); -}, -}; - - -scriptlets['prevent-requestAnimationFrame.js'] = { -aliases: ["no-requestAnimationFrame-if.js","norafif.js"], - -requiresTrust: false, -fn: 'preventRequestAnimationFrame', -dependencies: ["safeSelf","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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(); - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventRequestAnimationFrame(...args); -}, -}; - - -scriptlets['set-constant.js'] = { -aliases: ["set.js"], - -requiresTrust: false, -fn: 'setConstant', -dependencies: ["validateConstantFn","safeSelf","runAt","setConstantFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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 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); -} -function setConstant( - ...args -) { - setConstantFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -setConstant(...args); -}, -}; - - -scriptlets['trusted-set-constant.js'] = { -aliases: ["trusted-set.js"], - -requiresTrust: true, -fn: 'trustedSetConstant', -dependencies: ["validateConstantFn","safeSelf","runAt","setConstantFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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 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); -} -function trustedSetConstant( - ...args -) { - setConstantFn(true, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedSetConstant(...args); -}, -}; - - -scriptlets['trusted-replace-argument.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedReplaceArgument', -dependencies: ["validateConstantFn","safeSelf","proxyApplyFn","ArglistParser","parseReplaceFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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; -} -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 = '', - 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(); - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedReplaceArgument(...args); -}, -}; - - -scriptlets['spoof-css.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'spoofCSS', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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); - }, - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -spoofCSS(...args); -}, -}; - - -scriptlets['prevent-xhr.js'] = { -aliases: ["no-xhr-if.js"], - -requiresTrust: false, -fn: 'preventXhr', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","generateContentFn","preventXhrFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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 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; -} -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 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'); - }); -} -function preventXhr(...args) { - preventXhrFn(false, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventXhr(...args); -}, -}; - - -scriptlets['trusted-prevent-xhr.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'trustedPreventXhr', -dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","generateContentFn","preventXhrFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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 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; -} -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 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'); - }); -} -function trustedPreventXhr(...args) { - preventXhrFn(true, ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedPreventXhr(...args); -}, -}; - - -scriptlets['mpegdash-prune.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'mpegdashPrune', -dependencies: ["proxyToStringFn","parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","modifyXhrResponseFn"], -func: function (scriptletGlobals = {}, ...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 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 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; -} -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 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 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -mpegdashPrune(...args); -}, -}; - - -scriptlets['set-cookie.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -fn: 'setCookie', -dependencies: ["getCookieFn","setCookieFn","safeSelf","getSafeCookieValuesFn"], -func: function (scriptletGlobals = {}, ...args) { -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(); - } -} -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; -} -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; -} -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 = '', - 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'); - } -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -setCookie(...args); -}, -}; - - -scriptlets['set-cookie-reload.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -fn: 'setCookieReload', -dependencies: ["getCookieFn","setCookieFn","safeSelf","getSafeCookieValuesFn","setCookie"], -func: function (scriptletGlobals = {}, ...args) { -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(); - } -} -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; -} -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; -} -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 = '', - 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'); - } -} -function setCookieReload(name, value, path, ...args) { - setCookie(name, value, path, 'reload', '1', ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -setCookieReload(...args); -}, -}; - - -scriptlets['trusted-set-cookie.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -fn: 'trustedSetCookie', -dependencies: ["getCookieFn","setCookieFn","safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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(); - } -} -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; -} -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; -} -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'); - } -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedSetCookie(...args); -}, -}; - - -scriptlets['trusted-set-cookie-reload.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -fn: 'trustedSetCookieReload', -dependencies: ["getCookieFn","setCookieFn","safeSelf","trustedSetCookie"], -func: function (scriptletGlobals = {}, ...args) { -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(); - } -} -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; -} -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; -} -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'); - } -} -function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) { - trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedSetCookieReload(...args); -}, -}; - - -scriptlets['remove-cookie.js'] = { -aliases: ["cookie-remover.js"], -world: 'ISOLATED', -requiresTrust: false, -fn: 'removeCookie', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 }); - } -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -removeCookie(...args); -}, -}; - - -scriptlets['set-local-storage-item.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -fn: 'setLocalStorageItem', -dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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 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); - } -} -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; -} -function setLocalStorageItem(key = '', value = '', ...varargs) { - const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('local', false, key, value, options); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -setLocalStorageItem(...args); -}, -}; - - -scriptlets['set-session-storage-item.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -fn: 'setSessionStorageItem', -dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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 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); - } -} -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; -} -function setSessionStorageItem(key = '', value = '', ...varargs) { - const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('session', false, key, value, options); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -setSessionStorageItem(...args); -}, -}; - - -scriptlets['trusted-set-local-storage-item.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -fn: 'trustedSetLocalStorageItem', -dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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 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); - } -} -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; -} -function trustedSetLocalStorageItem(key = '', value = '', ...varargs) { - const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('local', true, key, value, options); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedSetLocalStorageItem(...args); -}, -}; - - -scriptlets['trusted-set-session-storage-item.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -fn: 'trustedSetSessionStorageItem', -dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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 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); - } -} -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; -} -function trustedSetSessionStorageItem(key = '', value = '', ...varargs) { - const safe = safeSelf(); - const options = safe.parseVarargs(varargs) - setLocalStorageItemFn('session', true, key, value, options); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedSetSessionStorageItem(...args); -}, -}; - - -scriptlets['abort-on-property-read.js'] = { -aliases: ["aopr.js"], - -requiresTrust: false, -fn: 'abortOnPropertyRead', -dependencies: ["safeSelf","getRandomTokenFn","getExceptionTokenFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -abortOnPropertyRead(...args); -}, -}; - - -scriptlets['abort-on-property-write.js'] = { -aliases: ["aopw.js"], - -requiresTrust: false, -fn: 'abortOnPropertyWrite', -dependencies: ["safeSelf","getRandomTokenFn","getExceptionTokenFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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); - } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -abortOnPropertyWrite(...args); -}, -}; - - -scriptlets['adjust-setInterval.js'] = { -aliases: ["nano-setInterval-booster.js","nano-sib.js"], - -requiresTrust: false, -fn: 'adjustSetInterval', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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); - } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -adjustSetInterval(...args); -}, -}; - - -scriptlets['adjust-setTimeout.js'] = { -aliases: ["nano-setTimeout-booster.js","nano-stb.js"], - -requiresTrust: false, -fn: 'adjustSetTimeout', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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); - } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -adjustSetTimeout(...args); -}, -}; - - -scriptlets['prevent-refresh.js'] = { -aliases: ["refresh-defuser.js"], -world: 'ISOLATED', -requiresTrust: false, -fn: 'preventRefresh', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventRefresh(...args); -}, -}; - - -scriptlets['remove-class.js'] = { -aliases: ["rc.js"], -world: 'ISOLATED', -requiresTrust: false, -fn: 'removeClass', -dependencies: ["safeSelf","runAt","onIdleFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 onIdleFn(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); -} -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'); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -removeClass(...args); -}, -}; - - -scriptlets['webrtc-if.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'webrtcIf', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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); - } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -webrtcIf(...args); -}, -}; - - -scriptlets['prevent-window-open.js'] = { -aliases: ["nowoif.js","no-window-open-if.js","window.open-defuser.js"], - -requiresTrust: false, -fn: 'noWindowOpenIf', -dependencies: ["safeSelf","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -noWindowOpenIf(...args); -}, -}; - - -scriptlets['close-window.js'] = { -aliases: ["window-close-if.js"], -world: 'ISOLATED', -requiresTrust: false, -fn: 'closeWindow', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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); - } -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -closeWindow(...args); -}, -}; - - -scriptlets['window.name-defuser.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'windowNameDefuser', -dependencies: [], -func: function (scriptletGlobals = {}, ...args) { - -function windowNameDefuser() { - if ( window === window.top ) { - window.name = ''; - } -}; -windowNameDefuser(...args); -}, -}; - - -scriptlets['overlay-buster.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'overlayBuster', -dependencies: [], -func: function (scriptletGlobals = {}, ...args) { - -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(); - } -}; -overlayBuster(...args); -}, -}; - - -scriptlets['alert-buster.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'alertBuster', -dependencies: [], -func: function (scriptletGlobals = {}, ...args) { - -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); - }, - }); -}; -alertBuster(...args); -}, -}; - - -scriptlets['nowebrtc.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'noWebrtc', -dependencies: [], -func: function (scriptletGlobals = {}, ...args) { - -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); - } -}; -noWebrtc(...args); -}, -}; - - -scriptlets['disable-newtab-links.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'disableNewtabLinks', -dependencies: [], -func: function (scriptletGlobals = {}, ...args) { - -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 }); -}; -disableNewtabLinks(...args); -}, -}; - - -scriptlets['xml-prune.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'xmlPrune', -dependencies: ["proxyToStringFn","parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","modifyXhrResponseFn"], -func: function (scriptletGlobals = {}, ...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 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 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; -} -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 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 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -xmlPrune(...args); -}, -}; - - -scriptlets['m3u-prune.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'm3uPrune', -dependencies: ["safeSelf","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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); - } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -m3uPrune(...args); -}, -}; - - -scriptlets['call-nothrow.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'callNothrow', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; - }, - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -callNothrow(...args); -}, -}; - - -scriptlets['remove-node-text.js'] = { -aliases: ["rmnt.js"], -world: 'ISOLATED', -requiresTrust: false, -fn: 'removeNodeText', -dependencies: ["runAt","safeSelf","getRandomTokenFn","replaceNodeTextFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -function getRandomTokenFn() { - const safe = safeSelf(); - return safe.String_fromCharCode(Date.now() % 26 + 97) + - safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); -} -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, - ...extraArgs -) { - replaceNodeTextFn(nodeName, '', '', 'includes', includes || '', ...extraArgs); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -removeNodeText(...args); -}, -}; - - -scriptlets['prevent-canvas.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'preventCanvas', -dependencies: ["safeSelf"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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); - } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -preventCanvas(...args); -}, -}; - - -scriptlets['multiup.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -fn: 'multiup', -dependencies: [], -func: function (scriptletGlobals = {}, ...args) { - -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 }); -}; -multiup(...args); -}, -}; - - -scriptlets['break-on-call.js'] = { -aliases: [], - -requiresTrust: false, -fn: 'breakOnCall', -dependencies: ["proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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 breakOnCall(target) { - proxyApplyFn(target, function(context) { - debugger; // eslint-disable-line no-debugger - return context.reflect(); - }); -}; -breakOnCall(...args); -}, -}; - - -scriptlets['trusted-replace-node-text.js'] = { -aliases: ["trusted-rpnt.js","replace-node-text.js","rpnt.js"], -world: 'ISOLATED', -requiresTrust: true, -fn: 'replaceNodeText', -dependencies: ["runAt","safeSelf","getRandomTokenFn","replaceNodeTextFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -function getRandomTokenFn() { - const safe = safeSelf(); - return safe.String_fromCharCode(Date.now() % 26 + 97) + - safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); -} -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 replaceNodeText( - nodeName, - pattern, - replacement, - ...extraArgs -) { - replaceNodeTextFn(nodeName, pattern, replacement, ...extraArgs); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -replaceNodeText(...args); -}, -}; - - -scriptlets['trusted-replace-fetch-response.js'] = { -aliases: ["trusted-rpfr.js"], - -requiresTrust: true, -fn: 'trustedReplaceFetchResponse', -dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","collateFetchArgumentsFn","replaceFetchResponseFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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 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); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedReplaceFetchResponse(...args); -}, -}; - - -scriptlets['trusted-replace-xhr-response.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedReplaceXhrResponse', -dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn"], -func: function (scriptletGlobals = {}, ...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 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; -} -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 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; - } - }; -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedReplaceXhrResponse(...args); -}, -}; - - -scriptlets['trusted-click-element.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: true, -fn: 'trustedClickElement', -dependencies: ["runAtHtmlElementFn","onIdleFn","offIdleFn","lookupElementsFn","getAllLocalStorageFn","safeSelf","getAllCookiesFn"], -func: function (scriptletGlobals = {}, ...args) { -function runAtHtmlElementFn(fn) { - if ( document.documentElement ) { - fn(); - return; - } - const observer = new MutationObserver(( ) => { - observer.disconnect(); - fn(); - }); - observer.observe(document, { childList: true }); -} -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 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 }); - }); -} -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 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; -} -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 = '', - 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); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedClickElement(...args); -}, -}; - - -scriptlets['trusted-replace-outbound-text.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedReplaceOutboundText', -dependencies: ["safeSelf","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedReplaceOutboundText(...args); -}, -}; - - -scriptlets['trusted-suppress-native-method.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedSuppressNativeMethod', -dependencies: ["proxyApplyFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn"], -func: function (scriptletGlobals = {}, ...args) { -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 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; -} -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 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; -} -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(); - } - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedSuppressNativeMethod(...args); -}, -}; - - -scriptlets['trusted-prevent-dom-bypass.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedPreventDomBypass', -dependencies: ["safeSelf","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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 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; - }); -}; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedPreventDomBypass(...args); -}, -}; - - -scriptlets['trusted-override-element-method.js'] = { -aliases: [], - -requiresTrust: true, -fn: 'trustedOverrideElementMethod', -dependencies: ["validateConstantFn","safeSelf","proxyApplyFn"], -func: function (scriptletGlobals = {}, ...args) { -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; -} -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; -} -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 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); - }); }; -try { - const key = Symbol.for('safeSelf.1.75.1b1'); - safeSelf.safe = globalThis[key]; - if ( safeSelf.safe === undefined ) { - Object.defineProperty(globalThis, key, { value: safeSelf() }); - } -} catch { -} -trustedOverrideElementMethod(...args); -}, + + +scriptlets['set-attr.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'setAttr', +dependencies: ["runAt","setAttrFn","safeSelf"], +}; + + +scriptlets['trusted-set-attr.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedSetAttr', +dependencies: ["runAt","setAttrFn","safeSelf"], +}; + + +scriptlets['remove-attr.js'] = { +aliases: ["ra.js"], + +requiresTrust: false, +fn: 'removeAttr', +dependencies: ["safeSelf","runAt","onIdleFn","offIdleFn"], +}; + + +scriptlets['trusted-create-html.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedCreateHTML', +dependencies: ["safeSelf","runAt"], +}; + + +scriptlets['href-sanitizer.js'] = { +aliases: ["urlskip.js"], +world: 'ISOLATED', +requiresTrust: false, +fn: 'hrefSanitizer', +dependencies: ["urlSkip","safeSelf","runAt","onIdleFn"], +}; + + +scriptlets['proxy-apply-config.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'proxyApplyConfig', +dependencies: [], +}; + + +scriptlets['edit-outbound-object.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editOutboundObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editOutboundObjectFn"], +}; + + +scriptlets['trusted-edit-outbound-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditOutboundObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editOutboundObjectFn"], +}; + + +scriptlets['json-edit.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonEdit', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","jsonEditFn"], +}; + + +scriptlets['trusted-json-edit.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonEdit', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","jsonEditFn"], +}; + + +scriptlets['edit-inbound-object.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editInboundObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editInboundObjectFn"], +}; + + +scriptlets['trusted-edit-inbound-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditInboundObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editInboundObjectFn"], +}; + + +scriptlets['edit-this-object.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editThisObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editThisObjectFn"], +}; + + +scriptlets['trusted-edit-this-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditThisObject', +dependencies: ["safeSelf","proxyApplyFn","JSONPath","editThisObjectFn"], +}; + + +scriptlets['edit-object-on-getter.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editObjectOnGetter', +dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnGetterFn"], +}; + + +scriptlets['trusted-edit-object-on-getter.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditObjectOnGetter', +dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnGetterFn"], +}; + + +scriptlets['edit-object-on-setter.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editObjectOnSetter', +dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnSetterFn"], +}; + + +scriptlets['trusted-edit-object-on-setter.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditObjectOnSetter', +dependencies: ["trapPropertyFn","safeSelf","JSONPath","editObjectOnSetterFn"], +}; + + +scriptlets['edit-element-object.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'editElementObject', +dependencies: ["sleepFn","safeSelf","onIdleFn","offIdleFn","lookupElementsFn","JSONPath","editElementObjectFn"], +}; + + +scriptlets['trusted-edit-element-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedEditElementObject', +dependencies: ["sleepFn","safeSelf","onIdleFn","offIdleFn","lookupElementsFn","JSONPath","editElementObjectFn"], +}; + + +scriptlets['json-edit-xhr-response.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonEditXhrResponse', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrResponseFn"], +}; + + +scriptlets['trusted-json-edit-xhr-response.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonEditXhrResponse', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrResponseFn"], +}; + + +scriptlets['json-edit-xhr-request.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonEditXhrRequest', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrRequestFn"], +}; + + +scriptlets['trusted-json-edit-xhr-request.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonEditXhrRequest', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","JSONPath","jsonEditXhrRequestFn"], +}; + + +scriptlets['json-edit-fetch-response.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonEditFetchResponse', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchResponseFn"], +}; + + +scriptlets['trusted-json-edit-fetch-response.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonEditFetchResponse', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchResponseFn"], +}; + + +scriptlets['json-edit-fetch-request.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonEditFetchRequest', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchRequestFn"], +}; + + +scriptlets['trusted-json-edit-fetch-request.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonEditFetchRequest', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonEditFetchRequestFn"], +}; + + +scriptlets['jsonl-edit-xhr-response.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonlEditXhrResponse', +dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","jsonlEditFn","JSONPath","jsonlEditXhrResponseFn"], +}; + + +scriptlets['trusted-jsonl-edit-xhr-response.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonlEditXhrResponse', +dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","jsonlEditFn","JSONPath","jsonlEditXhrResponseFn"], +}; + + +scriptlets['jsonl-edit-fetch-response.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonlEditFetchResponse', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","jsonlEditFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonlEditFetchResponseFn"], +}; + + +scriptlets['trusted-jsonl-edit-fetch-response.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedJsonlEditFetchResponse', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","jsonlEditFn","JSONPath","safeSelf","collateFetchArgumentsFn","jsonlEditFetchResponseFn"], +}; + + +scriptlets['abort-on-stack-trace.js'] = { +aliases: ["aost.js"], + +requiresTrust: false, +fn: 'abortOnStackTrace', +dependencies: ["matchesStackTraceFn","safeSelf","getRandomTokenFn","getExceptionTokenFn"], +}; + + +scriptlets['trusted-prune-inbound-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedPruneInboundObject', +dependencies: ["getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","safeSelf","objectFindOwnerFn"], +}; + + +scriptlets['trusted-prune-outbound-object.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedPruneOutboundObject', +dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], +}; + + +scriptlets['json-prune.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonPrune', +dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], +}; + + +scriptlets['json-prune-fetch-response.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonPruneFetchResponse', +dependencies: ["parsePropertiesToMatchFn","objectFindOwnerFn","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","matchObjectPropertiesFn","safeSelf","collateFetchArgumentsFn"], +}; + + +scriptlets['json-prune-xhr-response.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'jsonPruneXhrResponse', +dependencies: ["parsePropertiesToMatchFn","objectFindOwnerFn","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn","safeSelf","matchObjectPropertiesFn"], +}; + + +scriptlets['evaldata-prune.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'evaldataPrune', +dependencies: ["proxyApplyFn","objectFindOwnerFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn","objectPruneFn"], +}; + + +scriptlets['noeval-if.js'] = { +aliases: ["prevent-eval-if.js"], + +requiresTrust: false, +fn: 'noEvalIf', +dependencies: ["safeSelf","proxyApplyFn"], +}; + + +scriptlets['prevent-addEventListener.js'] = { +aliases: ["addEventListener-defuser.js","aeld.js"], + +requiresTrust: false, +fn: 'preventAddEventListener', +dependencies: ["safeSelf","runAt","proxyApplyFn"], +}; + + +scriptlets['prevent-bab.js'] = { +aliases: ["bab-defuser.js","nobab.js"], + +requiresTrust: false, +fn: 'preventBab', +dependencies: ["safeSelf","proxyApplyFn"], +}; + + +scriptlets['prevent-clipboard-write.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'preventClipboardWrite', +dependencies: ["safeSelf","runAt","proxyApplyFn"], +}; + + +scriptlets['prevent-dialog.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'preventDialog', +dependencies: ["safeSelf"], +}; + + +scriptlets['prevent-fetch.js'] = { +aliases: ["no-fetch-if.js"], + +requiresTrust: false, +fn: 'preventFetch', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","generateContentFn","safeSelf","collateFetchArgumentsFn","preventFetchFn"], +}; + + +scriptlets['trusted-prevent-fetch.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedPreventFetch', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","generateContentFn","safeSelf","collateFetchArgumentsFn","preventFetchFn"], +}; + + +scriptlets['freeze-element-property.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'freezeElementProperty', +dependencies: ["safeSelf"], +}; + + +scriptlets['prevent-innerHTML.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'preventInnerHTML', +dependencies: ["safeSelf","freezeElementProperty"], +}; + + +scriptlets['prevent-navigation.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'preventNavigation', +dependencies: ["safeSelf"], +}; + + +scriptlets['prevent-setTimeout.js'] = { +aliases: ["no-setTimeout-if.js","nostif.js","setTimeout-defuser.js"], + +requiresTrust: false, +fn: 'preventSetTimeout', +dependencies: ["safeSelf","RangeParser","proxyApplyFn"], +}; + + +scriptlets['prevent-setInterval.js'] = { +aliases: ["no-setInterval-if.js","nosiif.js","setInterval-defuser.js"], + +requiresTrust: false, +fn: 'preventSetInterval', +dependencies: ["safeSelf","RangeParser","proxyApplyFn"], +}; + + +scriptlets['prevent-requestAnimationFrame.js'] = { +aliases: ["no-requestAnimationFrame-if.js","norafif.js"], + +requiresTrust: false, +fn: 'preventRequestAnimationFrame', +dependencies: ["safeSelf","proxyApplyFn"], +}; + + +scriptlets['set-constant.js'] = { +aliases: ["set.js"], + +requiresTrust: false, +fn: 'setConstant', +dependencies: ["validateConstantFn","safeSelf","runAt","setConstantFn"], +}; + + +scriptlets['trusted-set-constant.js'] = { +aliases: ["trusted-set.js"], + +requiresTrust: true, +fn: 'trustedSetConstant', +dependencies: ["validateConstantFn","safeSelf","runAt","setConstantFn"], +}; + + +scriptlets['trusted-replace-argument.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedReplaceArgument', +dependencies: ["validateConstantFn","safeSelf","proxyApplyFn","ArglistParser","parseReplaceFn"], +}; + + +scriptlets['spoof-css.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'spoofCSS', +dependencies: ["safeSelf"], +}; + + +scriptlets['prevent-xhr.js'] = { +aliases: ["no-xhr-if.js"], + +requiresTrust: false, +fn: 'preventXhr', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","generateContentFn","preventXhrFn"], +}; + + +scriptlets['trusted-prevent-xhr.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'trustedPreventXhr', +dependencies: ["proxyApplyFn","parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","generateContentFn","preventXhrFn"], +}; + + +scriptlets['mpegdash-prune.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'mpegdashPrune', +dependencies: ["proxyToStringFn","parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","modifyXhrResponseFn"], +}; + + +scriptlets['set-cookie.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'setCookie', +dependencies: ["getCookieFn","setCookieFn","safeSelf","getSafeCookieValuesFn"], +}; + + +scriptlets['set-cookie-reload.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'setCookieReload', +dependencies: ["getCookieFn","setCookieFn","safeSelf","getSafeCookieValuesFn","setCookie"], +}; + + +scriptlets['trusted-set-cookie.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedSetCookie', +dependencies: ["getCookieFn","setCookieFn","safeSelf"], +}; + + +scriptlets['trusted-set-cookie-reload.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedSetCookieReload', +dependencies: ["getCookieFn","setCookieFn","safeSelf","trustedSetCookie"], +}; + + +scriptlets['remove-cookie.js'] = { +aliases: ["cookie-remover.js"], +world: 'ISOLATED', +requiresTrust: false, +fn: 'removeCookie', +dependencies: ["safeSelf"], +}; + + +scriptlets['set-local-storage-item.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'setLocalStorageItem', +dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], +}; + + +scriptlets['set-session-storage-item.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'setSessionStorageItem', +dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], +}; + + +scriptlets['trusted-set-local-storage-item.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedSetLocalStorageItem', +dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], +}; + + +scriptlets['trusted-set-session-storage-item.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedSetSessionStorageItem', +dependencies: ["getSafeCookieValuesFn","setLocalStorageItemFn","safeSelf"], +}; + + +scriptlets['abort-on-property-read.js'] = { +aliases: ["aopr.js"], + +requiresTrust: false, +fn: 'abortOnPropertyRead', +dependencies: ["safeSelf","getRandomTokenFn","getExceptionTokenFn"], +}; + + +scriptlets['abort-on-property-write.js'] = { +aliases: ["aopw.js"], + +requiresTrust: false, +fn: 'abortOnPropertyWrite', +dependencies: ["safeSelf","getRandomTokenFn","getExceptionTokenFn"], +}; + + +scriptlets['adjust-setInterval.js'] = { +aliases: ["nano-setInterval-booster.js","nano-sib.js"], + +requiresTrust: false, +fn: 'adjustSetInterval', +dependencies: ["safeSelf"], +}; + + +scriptlets['adjust-setTimeout.js'] = { +aliases: ["nano-setTimeout-booster.js","nano-stb.js"], + +requiresTrust: false, +fn: 'adjustSetTimeout', +dependencies: ["safeSelf"], +}; + + +scriptlets['prevent-refresh.js'] = { +aliases: ["refresh-defuser.js"], +world: 'ISOLATED', +requiresTrust: false, +fn: 'preventRefresh', +dependencies: ["safeSelf"], +}; + + +scriptlets['remove-class.js'] = { +aliases: ["rc.js"], +world: 'ISOLATED', +requiresTrust: false, +fn: 'removeClass', +dependencies: ["safeSelf","runAt","onIdleFn"], +}; + + +scriptlets['webrtc-if.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'webrtcIf', +dependencies: ["safeSelf"], +}; + + +scriptlets['prevent-window-open.js'] = { +aliases: ["nowoif.js","no-window-open-if.js","window.open-defuser.js"], + +requiresTrust: false, +fn: 'noWindowOpenIf', +dependencies: ["safeSelf","proxyApplyFn"], +}; + + +scriptlets['close-window.js'] = { +aliases: ["window-close-if.js"], +world: 'ISOLATED', +requiresTrust: false, +fn: 'closeWindow', +dependencies: ["safeSelf"], +}; + + +scriptlets['window.name-defuser.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'windowNameDefuser', +dependencies: [], +}; + + +scriptlets['overlay-buster.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'overlayBuster', +dependencies: [], +}; + + +scriptlets['alert-buster.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'alertBuster', +dependencies: [], +}; + + +scriptlets['nowebrtc.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'noWebrtc', +dependencies: [], +}; + + +scriptlets['disable-newtab-links.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'disableNewtabLinks', +dependencies: [], +}; + + +scriptlets['xml-prune.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'xmlPrune', +dependencies: ["proxyToStringFn","parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn","modifyXhrResponseFn"], +}; + + +scriptlets['m3u-prune.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'm3uPrune', +dependencies: ["safeSelf","proxyApplyFn"], +}; + + +scriptlets['call-nothrow.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'callNothrow', +dependencies: ["safeSelf"], +}; + + +scriptlets['remove-node-text.js'] = { +aliases: ["rmnt.js"], +world: 'ISOLATED', +requiresTrust: false, +fn: 'removeNodeText', +dependencies: ["runAt","safeSelf","getRandomTokenFn","replaceNodeTextFn"], +}; + + +scriptlets['prevent-canvas.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'preventCanvas', +dependencies: ["safeSelf"], +}; + + +scriptlets['multiup.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +fn: 'multiup', +dependencies: [], +}; + + +scriptlets['break-on-call.js'] = { +aliases: [], + +requiresTrust: false, +fn: 'breakOnCall', +dependencies: ["proxyApplyFn"], +}; + + +scriptlets['trusted-replace-node-text.js'] = { +aliases: ["trusted-rpnt.js","replace-node-text.js","rpnt.js"], +world: 'ISOLATED', +requiresTrust: true, +fn: 'replaceNodeText', +dependencies: ["runAt","safeSelf","getRandomTokenFn","replaceNodeTextFn"], +}; + + +scriptlets['trusted-replace-fetch-response.js'] = { +aliases: ["trusted-rpfr.js"], + +requiresTrust: true, +fn: 'trustedReplaceFetchResponse', +dependencies: ["parsePropertiesToMatchFn","matchObjectPropertiesFn","safeSelf","collateFetchArgumentsFn","replaceFetchResponseFn"], +}; + + +scriptlets['trusted-replace-xhr-response.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedReplaceXhrResponse', +dependencies: ["parsePropertiesToMatchFn","safeSelf","matchObjectPropertiesFn"], +}; + + +scriptlets['trusted-click-element.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: true, +fn: 'trustedClickElement', +dependencies: ["runAtHtmlElementFn","onIdleFn","offIdleFn","lookupElementsFn","getAllLocalStorageFn","safeSelf","getAllCookiesFn"], +}; + + +scriptlets['trusted-replace-outbound-text.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedReplaceOutboundText', +dependencies: ["safeSelf","proxyApplyFn"], +}; + + +scriptlets['trusted-suppress-native-method.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedSuppressNativeMethod', +dependencies: ["proxyApplyFn","safeSelf","getRandomTokenFn","getExceptionTokenFn","matchesStackTraceFn"], +}; + + +scriptlets['trusted-prevent-dom-bypass.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedPreventDomBypass', +dependencies: ["safeSelf","proxyApplyFn"], +}; + + +scriptlets['trusted-override-element-method.js'] = { +aliases: [], + +requiresTrust: true, +fn: 'trustedOverrideElementMethod', +dependencies: ["validateConstantFn","safeSelf","proxyApplyFn"], }; From d201f1188ff2dea47cb9fb4c5a252d0cbf12c366 Mon Sep 17 00:00:00 2001 From: Krzysztof Modras Date: Tue, 6 Oct 2026 14:07:49 +0200 Subject: [PATCH 3/3] Serve registered scripts from run() as well and drop compose() --- build.ts | 70 +- index.js | 23 +- test.js | 61 +- ubo.js | 8381 +++++++----------------------------------------------- 4 files changed, 1119 insertions(+), 7416 deletions(-) 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;