diff --git a/package.json b/package.json index 6148aca..e028f96 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "type": "module", "scripts": { - "build": "deno build.ts --tagName 1.73.1b0 > ubo.js", + "build": "deno build.ts --tagName 1.75.0 > ubo.js", "test": "node --test" }, "author": { diff --git a/ubo.js b/ubo.js index f5ea50e..a0c2167 100644 --- a/ubo.js +++ b/ubo.js @@ -119,8 +119,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -133,7 +132,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -144,10 +143,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -200,7 +197,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -486,8 +483,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -500,7 +496,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -511,10 +507,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -567,7 +561,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -791,8 +785,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -805,7 +798,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -816,10 +809,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -872,7 +863,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -989,8 +980,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -1003,7 +993,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -1014,10 +1004,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -1070,7 +1058,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -1201,71 +1189,95 @@ function offIdleFn(id) { function removeAttr( rawToken = '', rawSelector = '', - behavior = '' + behavior = '', + ...varargs ) { if ( typeof rawToken !== 'string' ) { return; } if ( rawToken === '' ) { return; } const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('remove-attr', 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 asap = /\basap\b/.test(behavior); - let timerId; - const rmattrAsync = ( ) => { - if ( timerId !== undefined ) { return; } - timerId = onIdleFn(( ) => { - timerId = undefined; + 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 rmattr = ( ) => { - if ( timerId !== undefined ) { - offIdleFn(timerId); - timerId = undefined; - } - try { - const nodes = document.querySelectorAll(selector); - for ( const node of nodes ) { - for ( const attr of tokens ) { - if ( node.hasAttribute(attr) === false ) { continue; } - node.removeAttribute(attr); - safe.uboLog(logPrefix, `Removed attribute '${attr}'`); + 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); } } - } catch { } }; - const mutationHandler = mutations => { - if ( timerId !== 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; } - } + 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'); } - if ( skip ) { return; } - asap ? rmattr() : rmattrAsync(); }; const start = ( ) => { rmattr(); - if ( /\bstay\b/.test(behavior) === false ) { return; } - const observer = new MutationObserver(mutationHandler); - observer.observe(document, { + 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+/)); }; @@ -1287,8 +1299,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -1301,7 +1312,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -1312,10 +1323,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -1368,7 +1377,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -1674,8 +1683,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -1688,7 +1696,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -1699,10 +1707,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -1755,7 +1761,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -2012,8 +2018,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -2026,7 +2031,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -2037,10 +2042,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -2093,7 +2096,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -2875,8 +2878,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -2889,7 +2891,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -2900,10 +2902,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -2956,7 +2956,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -3738,8 +3738,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -3752,7 +3751,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -3763,10 +3762,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -3819,7 +3816,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -4600,8 +4597,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -4614,7 +4610,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -4625,10 +4621,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -4681,7 +4675,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -5462,8 +5456,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -5476,7 +5469,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -5487,10 +5480,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -5543,7 +5534,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -6352,8 +6343,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -6366,7 +6356,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -6377,10 +6367,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -6433,7 +6421,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -7242,8 +7230,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -7256,7 +7243,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -7267,10 +7254,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -7323,7 +7308,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -8115,8 +8100,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -8129,7 +8113,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -8140,10 +8124,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -8196,7 +8178,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -9060,8 +9042,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -9074,7 +9055,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -9085,10 +9066,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -9141,7 +9120,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -9905,8 +9884,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -9919,7 +9897,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -9930,10 +9908,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -9986,7 +9962,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -10750,8 +10726,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -10764,7 +10739,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -10775,10 +10750,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -10831,7 +10804,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -11598,8 +11571,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -11612,7 +11584,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -11623,10 +11595,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -11679,7 +11649,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -12389,8 +12359,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -12403,7 +12372,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -12414,10 +12383,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -12470,7 +12437,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -13277,8 +13244,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -13291,7 +13257,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -13302,10 +13268,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -13358,7 +13322,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -14170,8 +14134,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -14184,7 +14147,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -14195,10 +14158,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -14251,7 +14212,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -15014,8 +14975,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -15028,7 +14988,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -15039,10 +14999,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -15095,7 +15053,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -15858,8 +15816,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -15872,7 +15829,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -15883,10 +15840,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -15939,7 +15894,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -16685,8 +16640,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -16699,7 +16653,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -16710,10 +16664,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -16766,7 +16718,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -18177,8 +18129,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -18191,7 +18142,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -18202,10 +18153,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -18258,7 +18207,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -19138,8 +19087,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -19152,7 +19100,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -19163,10 +19111,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -19219,7 +19165,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -20099,8 +20045,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -20113,7 +20058,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -20124,10 +20069,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -20180,7 +20123,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -21057,8 +21000,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -21071,7 +21013,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -21082,10 +21024,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -21138,7 +21078,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -21369,8 +21309,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -21383,7 +21322,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -21394,10 +21333,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -21450,7 +21387,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -22227,8 +22164,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -22241,7 +22177,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -22252,10 +22188,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -22308,7 +22242,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -23753,8 +23687,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -23767,7 +23700,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -23778,10 +23711,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -23834,7 +23765,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -24741,8 +24672,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -24755,7 +24685,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -24766,10 +24696,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -24822,7 +24750,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -25066,8 +24994,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -25080,7 +25007,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -25091,10 +25018,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -25147,7 +25072,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -25424,8 +25349,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -25438,7 +25362,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -25449,10 +25373,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -25505,7 +25427,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -25905,8 +25827,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -25919,7 +25840,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -25930,10 +25851,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -25986,7 +25905,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -26377,8 +26296,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -26391,7 +26309,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -26402,10 +26320,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -26458,7 +26374,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -26899,8 +26815,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -26913,7 +26828,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -26924,10 +26839,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -26980,7 +26893,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -27368,8 +27281,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -27382,7 +27294,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -27393,10 +27305,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -27449,7 +27359,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -27818,8 +27728,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -27832,7 +27741,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -27843,10 +27752,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -27899,7 +27806,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -28118,8 +28025,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -28132,7 +28038,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -28143,10 +28049,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -28199,7 +28103,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -28423,8 +28327,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -28437,7 +28340,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -28448,10 +28351,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -28504,7 +28405,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -28756,8 +28657,8 @@ function preventAddEventListener( return parts.join(''); }; const shouldPrevent = (thisArg, type, handler) => { - const matchesType = safe.RegExp_test.call(reType, type); - const matchesHandler = safe.RegExp_test.call(rePattern, 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 ) { @@ -28829,8 +28730,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -28843,7 +28743,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -28854,10 +28754,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -28910,7 +28808,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -29177,8 +29075,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -29191,7 +29088,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -29202,10 +29099,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -29258,7 +29153,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -29346,403 +29241,34 @@ 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)); +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 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 domAlert = clipboardText => { - const doc = document; - const div = doc.createElement('div'); - const span = doc.createElement('span'); - span.style = 'flex-grow:1;padding:0.5em 0 0.5em 0.5em;'; - const { domAlert } = extraArgs; - const placeholder = /\$\{text\}/.exec(domAlert); - if ( placeholder ) { - const code = doc.createElement('code'); - code.style = 'background-color:#ddc;font-family:monospace;padding:0.25em;user-select:none;word-break:break-all'; - code.textContent = clipboardText; - span.append( - domAlert.slice(0, placeholder.index), - code, - domAlert.slice(placeholder.index + placeholder[0].length) - ); - } else { - span.append(domAlert); - } - const button = doc.createElement('button'); - button.style = 'padding:1em'; - button.textContent = '×'; - button.addEventListener('click', ( ) => { - if ( currentAlert === null ) { return; } - currentAlert.remove(); - currentAlert = null; - }); - div.append(span, button); - div.style = 'background-color:beige;color:black;border:1px solid black;display:flex;font-size:medium;position:fixed;text-align:center;top:0;width:100%;z-index:2147483647'; - doc.documentElement.append(div); - if ( currentAlert ) { - currentAlert.remove(); - } - currentAlert = div; - }; - 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); + 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]; } - 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 ( text && prevent(text) ) { return false; } - } - return context.reflect(); - }, { skipToString: true }); + return 0; }; - self.addEventListener('mousedown', installTraps, { - once: true, - capture: true, - }); -}; -preventClipboardWrite(...args); -}, -}; - - -scriptlets['prevent-dialog.js'] = { -aliases: [], -world: 'ISOLATED', -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { -function safeSelf() { - if ( safeSelf.safe ) { - return safeSelf.safe; + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; } - const self = globalThis; - const safe = { - 'Array_from': Array.from, - 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), - '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': 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_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), - '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.call(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; + const onStateChange = ( ) => { + if ( intFromReadyState(document.readyState) < runAt ) { return; } + fn(); + safe.removeEventListener.apply(document, args); }; - 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, - }); -}; -preventDialog(...args); -}, -}; - - -scriptlets['prevent-fetch.js'] = { -aliases: ["no-fetch-if.js"], - -requiresTrust: false, -func: function (scriptletGlobals = {}, ...args) { + const args = [ 'readystatechange', onStateChange, { capture: true } ]; + safe.addEventListener.apply(document, args); +} function proxyApplyFn( target = '', handler = '', @@ -29841,110 +29367,106 @@ function proxyApplyFn( proxyApplyFn.proxies.set(proxiedTarget, fn); context[prop] = proxiedTarget; } -function parsePropertiesToMatchFn(propsToMatch, implicit = '') { +function preventClipboardWrite(matches = '', ...varargs) { 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('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); } - } - 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}`); + 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; + }); + doc.documentElement.append(currentAlert); + }; + 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; } } - } - 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; + if ( extraArgs.domAlert ) { + domAlert(text); } - while ( textSize < len ); - return chunks.join(' ').slice(0, len); + safe.uboLog(logPrefix, 'Prevented:\n\t', text); + return true; }; - 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 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 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 ''; -} + return context.reflect(); + }, { skipToString: true }); + }; + runAt(( ) => { + self.document.addEventListener('mousemove', installTraps, { + once: true, + capture: true, + }); + }, 'interactive') +}; +preventClipboardWrite(...args); +}, +}; + + +scriptlets['prevent-dialog.js'] = { +aliases: [], +world: 'ISOLATED', +requiresTrust: false, +func: function (scriptletGlobals = {}, ...args) { function safeSelf() { if ( safeSelf.safe ) { return safeSelf.safe; @@ -29953,8 +29475,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -29967,7 +29488,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -29978,10 +29499,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -30034,7 +29553,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -30122,133 +29641,41 @@ 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 = '', - ...varargs +function preventDialog( + selector = '', ) { 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(); + 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 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; - }); + }; + let debouncer; + const observer = new MutationObserver(( ) => { + if ( debouncer !== undefined ) { return; } + debouncer = requestAnimationFrame(prevent); + }); + observer.observe(document, { + attributes: true, + childList: true, + subtree: true, }); -} -function preventFetch(...args) { - preventFetchFn(false, ...args); }; -preventFetch(...args); +preventDialog(...args); }, }; -scriptlets['trusted-prevent-fetch.js'] = { -aliases: [], +scriptlets['prevent-fetch.js'] = { +aliases: ["no-fetch-if.js"], -requiresTrust: true, +requiresTrust: false, func: function (scriptletGlobals = {}, ...args) { function proxyApplyFn( target = '', @@ -30460,8 +29887,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -30474,7 +29900,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -30485,10 +29911,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -30541,7 +29965,511 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + 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); +}; +preventFetch(...args); +}, +}; + + +scriptlets['trusted-prevent-fetch.js'] = { +aliases: [], + +requiresTrust: true, +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; }, @@ -30765,8 +30693,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -30779,7 +30706,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -30790,10 +30717,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -30846,7 +30771,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -31004,8 +30929,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -31018,7 +30942,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -31029,10 +30953,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -31085,7 +31007,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -31249,8 +31171,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -31263,7 +31184,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -31274,10 +31195,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -31330,7 +31249,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -31456,8 +31375,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -31470,7 +31388,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -31481,10 +31399,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -31537,7 +31453,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -31797,8 +31713,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -31811,7 +31726,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -31822,10 +31737,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -31878,7 +31791,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -32138,8 +32051,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -32152,7 +32064,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -32163,10 +32075,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -32219,7 +32129,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -32431,147 +32341,61 @@ preventRequestAnimationFrame(...args); }; -scriptlets['prevent-xhr.js'] = { -aliases: ["no-xhr-if.js"], +scriptlets['set-constant.js'] = { +aliases: ["set.js"], 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 = '') { +function validateConstantFn(trusted, raw, extraArgs = {}) { 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)); + 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; } - 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}`); + 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 ) { @@ -32581,8 +32405,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -32595,7 +32418,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -32606,10 +32429,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -32662,7 +32483,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -32750,387 +32571,249 @@ 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; +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]; } - while ( textSize < len ); - return chunks.join(' ').slice(0, len); + return 0; }; - 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; + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; } - 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 preventXhrFn( +function setConstantFn( trusted = false, - propsToMatch = '', - directive = '' + chain = '', + rawValue = '', + ...varargs ) { - if ( typeof propsToMatch !== 'string' ) { return; } + if ( chain === '' ) { 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': '', + 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; }, - url: haystack.url, - props: { - response: { value: '' }, - responseText: { value: '' }, - responseXML: { value: null }, + 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); }, }); - 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; + }; + if ( trappedProp === '' ) { return; } + const thisScript = document.currentScript; + let normalValue = validateConstantFn(trusted, rawValue, extraArgs); + if ( rawValue === 'noopFunc' || rawValue === 'trueFunc' || rawValue === 'falseFunc' ) { + normalValue = cloakFunc(normalValue); } - 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; + 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); + } }); - } else { - xhrDetails.props.response.value = xhrText; - xhrDetails.props.responseText.value = xhrText; + safe.uboLog(logPrefix, 'Trap installed'); + } catch(ex) { + safe.uboErr(logPrefix, ex); } - 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' }, + }; + 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); + } + } }); - 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'); - }); + }; + trapChain(window, chain); + } + runAt(( ) => { + setConstant(chain, rawValue); + }, extraArgs.runAt); } -function preventXhr(...args) { - preventXhrFn(false, ...args); +function setConstant( + ...args +) { + setConstantFn(false, ...args); }; -preventXhr(...args); +setConstant(...args); }, }; -scriptlets['trusted-prevent-xhr.js'] = { -aliases: [], +scriptlets['trusted-set-constant.js'] = { +aliases: ["trusted-set.js"], -requiresTrust: false, +requiresTrust: true, 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 = '') { +function validateConstantFn(trusted, raw, extraArgs = {}) { 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)); + 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; } - 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}`); + 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 ) { @@ -33140,8 +32823,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -33154,7 +32836,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -33165,10 +32847,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -33221,7 +32901,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -33309,601 +32989,128 @@ 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; +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]; } - while ( textSize < len ); - return chunks.join(' ').slice(0, len); + return 0; }; - 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; + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; } - 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 preventXhrFn( +function setConstantFn( trusted = false, - propsToMatch = '', - directive = '' + chain = '', + rawValue = '', + ...varargs ) { - if ( typeof propsToMatch !== 'string' ) { return; } + if ( chain === '' ) { 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': '', + 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; }, - url: haystack.url, - props: { - response: { value: '' }, - responseText: { value: '' }, - responseXML: { value: null }, + 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); }, }); - 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; + }; + if ( trappedProp === '' ) { return; } + const thisScript = document.currentScript; + let normalValue = validateConstantFn(trusted, rawValue, extraArgs); + if ( rawValue === 'noopFunc' || rawValue === 'trueFunc' || rawValue === 'falseFunc' ) { + normalValue = cloakFunc(normalValue); } - 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; + 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); + } }); - } 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); -}; -trustedPreventXhr(...args); -}, -}; - - -scriptlets['set-constant.js'] = { -aliases: ["set.js"], - -requiresTrust: false, -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_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), - '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': 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_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), - '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.call(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); + safe.uboLog(logPrefix, 'Trap installed'); + } catch(ex) { + safe.uboErr(logPrefix, ex); } }; const trapChain = function(owner, chain) { @@ -33960,18 +33167,18 @@ function setConstantFn( setConstant(chain, rawValue); }, extraArgs.runAt); } -function setConstant( +function trustedSetConstant( ...args ) { - setConstantFn(false, ...args); + setConstantFn(true, ...args); }; -setConstant(...args); +trustedSetConstant(...args); }, }; -scriptlets['trusted-set-constant.js'] = { -aliases: ["trusted-set.js"], +scriptlets['trusted-replace-argument.js'] = { +aliases: [], requiresTrust: true, func: function (scriptletGlobals = {}, ...args) { @@ -34034,8 +33241,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -34048,7 +33254,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -34059,10 +33265,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -34115,7 +33319,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -34203,249 +33407,1285 @@ 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, +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); + } }; - 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.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 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); + 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(); + }); +}; +trustedReplaceArgument(...args); +}, +}; + + +scriptlets['spoof-css.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; } + } + 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); + }, + }); +}; +spoofCSS(...args); +}, +}; + + +scriptlets['prevent-xhr.js'] = { +aliases: ["no-xhr-if.js"], + +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; +} +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 setConstantFn( +function preventXhrFn( trusted = false, - chain = '', - rawValue = '', - ...varargs + propsToMatch = '', + directive = '' ) { - if ( chain === '' ) { return; } + if ( typeof propsToMatch !== '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; + 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': '', }, - get(target, prop) { - if ( prop === 'toString' ) { - return function() { - return `function ${trappedProp}() { [native code] }`; - }.bind(null); - } - return Reflect.get(...arguments); + url: haystack.url, + props: { + response: { value: '' }, + responseText: { value: '' }, + responseXML: { value: null }, }, }); - }; - if ( trappedProp === '' ) { return; } - const thisScript = document.currentScript; - let normalValue = validateConstantFn(trusted, rawValue, extraArgs); - if ( rawValue === 'noopFunc' || rawValue === 'trueFunc' || rawValue === 'falseFunc' ) { - normalValue = cloakFunc(normalValue); + xhrInstances.set(thisArg, xhrDetails); } - 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 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; }); - 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; + } else { + xhrDetails.props.response.value = xhrText; + xhrDetails.props.responseText.value = xhrText; } - 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); - } - } + 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' }, }); - }; - trapChain(window, chain); - } - runAt(( ) => { - setConstant(chain, rawValue); - }, extraArgs.runAt); + 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 trustedSetConstant( - ...args -) { - setConstantFn(true, ...args); +function preventXhr(...args) { + preventXhrFn(false, ...args); }; -trustedSetConstant(...args); +preventXhr(...args); }, }; -scriptlets['trusted-replace-argument.js'] = { +scriptlets['trusted-prevent-xhr.js'] = { aliases: [], -requiresTrust: true, +requiresTrust: false, 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 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; } - } 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 ( 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 value; + 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 ) { @@ -34455,8 +34695,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -34469,7 +34708,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -34480,10 +34719,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -34536,7 +34773,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -34624,292 +34861,291 @@ 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 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)); - }; - } - 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 = /\\+$/; + if ( directive === 'true' ) { + return randomize(10); } - 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 ( directive === 'emptyObj' ) { + return '{}'; } - 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; + if ( directive === 'emptyArr' ) { + return '[]'; } - leftWhitespaceCount(s) { - const match = this.reWhitespaceStart.exec(s); - return match === null ? 0 : match[0].length; + if ( directive === 'emptyStr' ) { + return ''; } - rightWhitespaceCount(s) { - const match = this.reWhitespaceEnd.exec(s); - return match === null ? 0 : match[1].length; + 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); } - 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); + 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); } - 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); + const warXHR = new safe.XMLHttpRequest(); + warXHR.responseType = 'text'; + warXHR.onloadend = ev => { + resolve(ev.target.responseText || ''); + }; + warXHR.open('GET', fullpath.join('')); + warXHR.send(); + }).catch(( ) => ''); } - 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); + 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(''); } - const flags = s.slice(parser.separatorEnd); - try { - return { re: new RegExp(pattern, flags), replacement }; - } catch { + if ( trusted ) { + return directive; } + return ''; } -function trustedReplaceArgument( - propChain = '', - argposRaw = '', - argraw = '', - ...varargs +function preventXhrFn( + trusted = false, + propsToMatch = '', + directive = '' ) { - if ( propChain === '' ) { return; } + if ( typeof propsToMatch !== '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; + 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(propChain, function(context) { - if ( argposRaw === '' ) { - safe.uboLog(logPrefix, `Arguments:\n${context.callArgs.join('\n')}`); + 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 argBefore = getArg(context); - if ( extraArgs.condition !== undefined ) { - if ( safe.RegExp_test.call(reCondition, argBefore) === false ) { - return context.reflect(); - } + const haystack = { method, url }; + if ( propsToMatch === '' && directive === '' ) { + safe.uboLog(logPrefix, `Called: ${safe.JSON_stringify(haystack, null, 2)}`); + return context.reflect(); } - const argAfter = replacer(argBefore); - if ( argAfter !== argBefore ) { - setArg(context, argAfter); - safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`); + 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); }; -trustedReplaceArgument(...args); +trustedPreventXhr(...args); }, }; -scriptlets['spoof-css.js'] = { +scriptlets['mpegdash-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); +} +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; @@ -34918,8 +35154,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -34932,7 +35167,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -34943,10 +35178,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -34999,7 +35232,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -35087,124 +35320,212 @@ function safeSelf() { } return safe; } -function spoofCSS( - selector, - ...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; } + } + 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 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}`); + const logPrefix = safe.makeLogPrefix('mpegdash-prune', selector, propsToMatch); + const queryAll = (xmlDoc, selector) => { + if ( selector.startsWith('xpath:') === false ) { + return Array.from(xmlDoc.querySelectorAll(selector)); } - return value; + 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 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 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(''); }; - 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')); + 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)}`); } - return new self.DOMRect(x, y, width, height); - }, - get(target, prop) { - if ( prop === 'toString' ) { - return target.toString.bind(target); + 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'); } - return Reflect.get(target, prop); - }, + 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; }); }; -spoofCSS(...args); +mpegdashPrune(...args); }, }; @@ -35299,8 +35620,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -35313,7 +35633,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -35324,10 +35644,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -35380,7 +35698,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -35492,6 +35810,8 @@ function getSafeCookieValuesFn() { 'decline', 'declined', 'closed', 'next', 'mandatory', 'disagree', 'agree', + 'set', 'unset', + 'given', ]; } function setCookie( @@ -35621,8 +35941,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -35635,7 +35954,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -35646,10 +35965,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -35702,7 +36019,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -35814,6 +36131,8 @@ function getSafeCookieValuesFn() { 'decline', 'declined', 'closed', 'next', 'mandatory', 'disagree', 'agree', + 'set', 'unset', + 'given', ]; } function setCookie( @@ -35946,8 +36265,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -35960,7 +36278,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -35971,10 +36289,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -36027,7 +36343,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -36259,8 +36575,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -36273,7 +36588,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -36284,10 +36599,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -36340,7 +36653,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -36498,8 +36811,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -36512,7 +36824,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -36523,10 +36835,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -36579,7 +36889,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -36773,6 +37083,8 @@ function getSafeCookieValuesFn() { 'decline', 'declined', 'closed', 'next', 'mandatory', 'disagree', 'agree', + 'set', 'unset', + 'given', ]; } function setLocalStorageItemFn( @@ -36860,8 +37172,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -36874,7 +37185,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -36885,10 +37196,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -36941,7 +37250,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -37068,6 +37377,8 @@ function getSafeCookieValuesFn() { 'decline', 'declined', 'closed', 'next', 'mandatory', 'disagree', 'agree', + 'set', 'unset', + 'given', ]; } function setLocalStorageItemFn( @@ -37155,8 +37466,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -37169,7 +37479,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -37180,10 +37490,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -37236,7 +37544,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -37363,6 +37671,8 @@ function getSafeCookieValuesFn() { 'decline', 'declined', 'closed', 'next', 'mandatory', 'disagree', 'agree', + 'set', 'unset', + 'given', ]; } function setLocalStorageItemFn( @@ -37450,8 +37760,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -37464,7 +37773,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -37475,10 +37784,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -37531,7 +37838,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -37658,6 +37965,8 @@ function getSafeCookieValuesFn() { 'decline', 'declined', 'closed', 'next', 'mandatory', 'disagree', 'agree', + 'set', 'unset', + 'given', ]; } function setLocalStorageItemFn( @@ -37745,8 +38054,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -37759,7 +38067,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -37770,10 +38078,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -37826,7 +38132,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -37937,8 +38243,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -37951,7 +38256,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -37962,10 +38267,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -38018,7 +38321,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -38186,8 +38489,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -38200,7 +38502,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -38211,10 +38513,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -38267,7 +38567,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -38413,8 +38713,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -38427,7 +38726,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -38438,10 +38737,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -38494,7 +38791,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -38627,8 +38924,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -38641,7 +38937,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -38652,10 +38948,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -38708,7 +39002,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -38841,8 +39135,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -38855,7 +39148,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -38866,10 +39159,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -38922,7 +39213,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -39053,8 +39344,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -39067,7 +39357,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -39078,10 +39368,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -39134,7 +39422,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -39336,8 +39624,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -39350,7 +39637,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -39361,10 +39648,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -39417,7 +39702,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -39581,8 +39866,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -39595,7 +39879,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -39606,10 +39890,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -39662,7 +39944,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -39951,8 +40233,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -39965,7 +40246,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -39976,10 +40257,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -40032,7 +40311,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -40317,6 +40596,46 @@ 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); +} +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; @@ -40325,8 +40644,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -40339,7 +40657,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -40350,10 +40668,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -40406,7 +40722,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -40494,6 +40810,69 @@ 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; +} +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 = '', @@ -40596,41 +40975,14 @@ function xmlPrune( }); } }); - 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 === 'document' || - type === '' && thisArg.responseXML instanceof XMLDocument - ) { - pruneFromDoc(thisArg.responseXML); - const serializer = new XMLSerializer(); - const textout = serializer.serializeToString(thisArg.responseXML); - Object.defineProperty(thisArg, 'responseText', { value: textout }); - if ( typeof thisArg.response === 'string' ) { - Object.defineProperty(thisArg, 'response', { value: textout }); - } - return; - } - if ( - type === 'text' || - type === '' && typeof thisArg.responseText === 'string' - ) { - const textin = thisArg.responseText; - const textout = pruneFromText(textin); - if ( textout === textin ) { return; } - Object.defineProperty(thisArg, 'response', { value: textout }); - Object.defineProperty(thisArg, 'responseText', { value: textout }); - return; - } - }); - return Reflect.apply(target, thisArg, args); + modifyXhrResponseFn(urlPattern, (xhr, before) => { + if ( before instanceof XMLDocument ) { + return pruneFromDoc(before); + } + if ( typeof before === 'string' ) { + return pruneFromText(before); } + return before; }); }; xmlPrune(...args); @@ -40651,8 +41003,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -40665,7 +41016,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -40676,10 +41027,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -40732,7 +41081,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -41094,8 +41443,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -41108,7 +41456,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -41119,10 +41467,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -41175,7 +41521,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -41337,8 +41683,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -41351,7 +41696,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -41362,10 +41707,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -41418,7 +41761,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -41528,15 +41871,6 @@ function replaceNodeTextFn( const reExcludes = extraArgs.excludes ? safe.patternToRegex(extraArgs.excludes, 'ms') : null; - const stop = (takeRecord = true) => { - if ( takeRecord ) { - handleMutations(observer.takeRecords()); - } - observer.disconnect(); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, 'Quitting'); - } - }; const textContentFactory = (( ) => { const out = { createScript: s => s }; const { trustedTypes: tt } = self; @@ -41549,19 +41883,19 @@ function replaceNodeTextFn( } return out; })(); - let sedCount = extraArgs.sedCount || 0; + let sedCount = extraArgs.sedCount ?? Number.MAX_SAFE_INTEGER; const handleNode = node => { const before = node.textContent; if ( reIncludes ) { reIncludes.lastIndex = 0; - if ( safe.RegExp_test.call(reIncludes, before) === false ) { return true; } + if ( safe.RegExp_test(reIncludes, before) === false ) { return; } } if ( reExcludes ) { reExcludes.lastIndex = 0; - if ( safe.RegExp_test.call(reExcludes, before) ) { return true; } + if ( safe.RegExp_test(reExcludes, before) ) { return; } } rePattern.lastIndex = 0; - if ( safe.RegExp_test.call(rePattern, before) === false ) { return true; } + if ( safe.RegExp_test(rePattern, before) === false ) { return; } rePattern.lastIndex = 0; const after = pattern !== '' ? before.replace(rePattern, replacement) @@ -41573,44 +41907,65 @@ function replaceNodeTextFn( safe.uboLog(logPrefix, `Text before:\n${before.trim()}`); } safe.uboLog(logPrefix, `Text after:\n${after.trim()}`); - return sedCount === 0 || (sedCount -= 1) !== 0; + sedCount -= 1; }; - const handleMutations = mutations => { - for ( const mutation of mutations ) { - for ( const node of mutation.addedNodes ) { - if ( reNodeName.test(node.nodeName) === false ) { continue; } - if ( handleNode(node) ) { continue; } - stop(false); return; - } - } - }; - const observer = new MutationObserver(handleMutations); - observer.observe(document, { childList: true, subtree: true }); - if ( document.documentElement ) { - const treeWalker = document.createTreeWalker( - document.documentElement, + 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(); - count += 1; if ( node === null ) { break; } - if ( reNodeName.test(node.nodeName) === false ) { continue; } - if ( node === document.currentScript ) { continue; } - if ( handleNode(node) ) { continue; } - stop(); 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`); } - if ( extraArgs.stay ) { return; } - runAt(( ) => { - const quitAfter = extraArgs.quitAfter || 0; - if ( quitAfter !== 0 ) { - setTimeout(( ) => { stop(); }, quitAfter); - } else { - stop(); + 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( @@ -41638,8 +41993,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -41652,7 +42006,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -41663,10 +42017,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -41719,7 +42071,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -42006,8 +42358,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -42020,7 +42371,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -42031,10 +42382,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -42087,7 +42436,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -42197,15 +42546,6 @@ function replaceNodeTextFn( const reExcludes = extraArgs.excludes ? safe.patternToRegex(extraArgs.excludes, 'ms') : null; - const stop = (takeRecord = true) => { - if ( takeRecord ) { - handleMutations(observer.takeRecords()); - } - observer.disconnect(); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, 'Quitting'); - } - }; const textContentFactory = (( ) => { const out = { createScript: s => s }; const { trustedTypes: tt } = self; @@ -42218,19 +42558,19 @@ function replaceNodeTextFn( } return out; })(); - let sedCount = extraArgs.sedCount || 0; + let sedCount = extraArgs.sedCount ?? Number.MAX_SAFE_INTEGER; const handleNode = node => { const before = node.textContent; if ( reIncludes ) { reIncludes.lastIndex = 0; - if ( safe.RegExp_test.call(reIncludes, before) === false ) { return true; } + if ( safe.RegExp_test(reIncludes, before) === false ) { return; } } if ( reExcludes ) { reExcludes.lastIndex = 0; - if ( safe.RegExp_test.call(reExcludes, before) ) { return true; } + if ( safe.RegExp_test(reExcludes, before) ) { return; } } rePattern.lastIndex = 0; - if ( safe.RegExp_test.call(rePattern, before) === false ) { return true; } + if ( safe.RegExp_test(rePattern, before) === false ) { return; } rePattern.lastIndex = 0; const after = pattern !== '' ? before.replace(rePattern, replacement) @@ -42242,44 +42582,65 @@ function replaceNodeTextFn( safe.uboLog(logPrefix, `Text before:\n${before.trim()}`); } safe.uboLog(logPrefix, `Text after:\n${after.trim()}`); - return sedCount === 0 || (sedCount -= 1) !== 0; - }; - const handleMutations = mutations => { - for ( const mutation of mutations ) { - for ( const node of mutation.addedNodes ) { - if ( reNodeName.test(node.nodeName) === false ) { continue; } - if ( handleNode(node) ) { continue; } - stop(false); return; - } - } + sedCount -= 1; }; - const observer = new MutationObserver(handleMutations); - observer.observe(document, { childList: true, subtree: true }); - if ( document.documentElement ) { - const treeWalker = document.createTreeWalker( - document.documentElement, + 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(); - count += 1; if ( node === null ) { break; } - if ( reNodeName.test(node.nodeName) === false ) { continue; } - if ( node === document.currentScript ) { continue; } - if ( handleNode(node) ) { continue; } - stop(); 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`); } - if ( extraArgs.stay ) { return; } - runAt(( ) => { - const quitAfter = extraArgs.quitAfter || 0; - if ( quitAfter !== 0 ) { - setTimeout(( ) => { stop(); }, quitAfter); - } else { - stop(); + 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( @@ -42347,8 +42708,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -42361,7 +42721,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -42372,10 +42732,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -42428,7 +42786,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -42650,8 +43008,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -42664,7 +43021,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -42675,10 +43032,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -42731,7 +43086,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -43047,8 +43402,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -43061,7 +43415,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -43072,10 +43426,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -43128,7 +43480,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -43362,8 +43714,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -43376,7 +43727,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -43387,10 +43738,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -43443,7 +43792,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -43637,7 +43986,7 @@ function trustedReplaceOutboundText( ) { if ( propChain === '' ) { return; } const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('trusted-replace-outbound-text', propChain, rawPattern, rawReplacement, ...args); + 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)) @@ -43786,8 +44135,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -43800,7 +44148,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -43811,10 +44159,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -43867,7 +44213,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -44061,7 +44407,7 @@ function trustedSuppressNativeMethod( } } if ( signatureArg.type === 'pattern' ) { - if ( safe.RegExp_test.call(signatureArg.re, targetArg) === false ) { + if ( safe.RegExp_test(signatureArg.re, targetArg) === false ) { return context.reflect(); } } @@ -44100,8 +44446,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -44114,7 +44459,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -44125,10 +44470,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -44181,7 +44524,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; }, @@ -44476,8 +44819,7 @@ function safeSelf() { const safe = { 'Array_from': Array.from, 'Error': self.Error, - 'Function_toStringFn': self.Function.prototype.toString, - 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Function_toString': Function.prototype.call.bind(self.Function.prototype.toString), 'Math_floor': Math.floor, 'Math_max': Math.max, 'Math_min': Math.min, @@ -44490,7 +44832,7 @@ function safeSelf() { 'Object_hasOwn': Object.hasOwn.bind(Object), 'Object_toString': Object.prototype.toString, 'RegExp': self.RegExp, - 'RegExp_test': self.RegExp.prototype.test, + '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, @@ -44501,10 +44843,8 @@ function safeSelf() { 'removeEventListener': self.EventTarget.prototype.removeEventListener, 'fetch': self.fetch, 'JSON': self.JSON, - 'JSON_parseFn': self.JSON.parse, - 'JSON_stringifyFn': self.JSON.stringify, - 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), - 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + '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, @@ -44557,7 +44897,7 @@ function safeSelf() { testPattern(details, haystack) { if ( details.matchAll ) { return true; } if ( details.re ) { - return this.RegExp_test.call(details.re, haystack) === details.expect; + return this.RegExp_test(details.re, haystack) === details.expect; } return haystack.includes(details.pattern) === details.expect; },