From 3d1ffdac3838d6cf0a8ec966c83c4d6f43c688a0 Mon Sep 17 00:00:00 2001 From: Ghostery Adblocker Bot Date: Fri, 5 Jun 2026 20:42:17 +0000 Subject: [PATCH] Update scriptlets --- package.json | 2 +- ubo.js | 4126 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 2857 insertions(+), 1271 deletions(-) diff --git a/package.json b/package.json index 783303f..5ed9bcb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "type": "module", "scripts": { - "build": "deno build.ts --tagName 1.71.1b0 > ubo.js", + "build": "deno build.ts --tagName 1.71.1b3 > ubo.js", "test": "node --test" }, "author": { diff --git a/ubo.js b/ubo.js index be9a497..e164a28 100644 --- a/ubo.js +++ b/ubo.js @@ -2006,7 +2006,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -2781,7 +2781,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -3556,7 +3556,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -4331,7 +4331,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -5106,7 +5106,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -5908,7 +5908,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -6376,31 +6376,11 @@ trustedEditInboundObject(...args); }; -scriptlets['json-edit-xhr-response.js'] = { +scriptlets['edit-this-object.js'] = { aliases: [], requiresTrust: false, func: function (scriptletGlobals = {}, ...args) { -function parsePropertiesToMatchFn(propsToMatch, implicit = '') { - const safe = safeSelf(); - const needles = new Map(); - if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } - const options = { canNegate: true }; - for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { - let [ prop, pattern ] = safe.String_split.call(needle, ':'); - if ( prop === '' ) { continue; } - if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { - prop = `${prop}:${pattern}`; - pattern = undefined; - } - if ( pattern !== undefined ) { - needles.set(prop, safe.initPattern(pattern, options)); - } else if ( implicit !== '' ) { - needles.set(implicit, safe.initPattern(prop, options)); - } - } - return needles; -} function safeSelf() { if ( scriptletGlobals.safeSelf ) { return scriptletGlobals.safeSelf; @@ -6591,24 +6571,100 @@ function safeSelf() { } return safe; } -function matchObjectPropertiesFn(propNeedles, ...objs) { - const safe = safeSelf(); - const matched = []; - for ( const obj of objs ) { - if ( obj instanceof Object === false ) { continue; } - for ( const [ prop, details ] of propNeedles ) { - let value = obj[prop]; - if ( value === undefined ) { continue; } - if ( typeof value !== 'string' ) { - try { value = safe.JSON_stringify(value); } - catch { } - if ( typeof value !== 'string' ) { continue; } +function proxyApplyFn( + target = '', + handler = '' +) { + let context = globalThis; + let prop = target; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); + } + const fn = context[prop]; + if ( typeof fn !== 'function' ) { return; } + if ( proxyApplyFn.CtorContext === undefined ) { + proxyApplyFn.ctorContexts = []; + proxyApplyFn.CtorContext = class { + constructor(...args) { + this.init(...args); } - if ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); + } + }; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); + } + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs); + this.callFn = this.thisArg = this.callArgs = this.private = undefined; + proxyApplyFn.applyContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); + } + }; + proxyApplyFn.isCtor = new Map(); + proxyApplyFn.proxies = new WeakMap(); + proxyApplyFn.nativeToString = Function.prototype.toString; + const proxiedToString = new Proxy(Function.prototype.toString, { + apply(target, thisArg) { + let proxied = thisArg; + for(;;) { + const fn = proxyApplyFn.proxies.get(proxied); + if ( fn === undefined ) { break; } + proxied = fn; + } + return proxyApplyFn.nativeToString.call(proxied); + } + }); + proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); + Function.prototype.toString = proxiedToString; + } + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); + } + const proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); } + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; } - return matched; + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; } class JSONPath { static create(query) { @@ -6654,7 +6710,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -7060,106 +7116,56 @@ class JSONPath { } } } -function jsonEditXhrResponseFn(trusted, jsonq = '') { +function editThisObjectFn( + trusted = false, + propChain = '', + jsonq = '', + order = '' +) { + if ( propChain === '' ) { return; } const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-xhr-response`, + `${trusted ? 'trusted-' : ''}edit-this-object`, + propChain, jsonq ); - const xhrInstances = new WeakMap(); const jsonp = JSONPath.create(jsonq); if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { return safe.uboLog(logPrefix, 'Bad JSONPath query'); } - const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - self.XMLHttpRequest = class extends self.XMLHttpRequest { - open(method, url, ...args) { - const xhrDetails = { method, url }; - const matched = propNeedles.size === 0 || - matchObjectPropertiesFn(propNeedles, xhrDetails); - if ( matched ) { - if ( safe.logLevel > 1 && Array.isArray(matched) ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - xhrInstances.set(this, xhrDetails); - } - return super.open(method, url, ...args); - } - get response() { - const innerResponse = super.response; - const xhrDetails = xhrInstances.get(this); - if ( xhrDetails === undefined ) { return innerResponse; } - const responseLength = typeof innerResponse === 'string' - ? innerResponse.length - : undefined; - if ( xhrDetails.lastResponseLength !== responseLength ) { - xhrDetails.response = undefined; - xhrDetails.lastResponseLength = responseLength; - } - if ( xhrDetails.response !== undefined ) { - return xhrDetails.response; - } - let obj; - if ( typeof innerResponse === 'object' ) { - obj = innerResponse; - } else if ( typeof innerResponse === 'string' ) { - try { obj = safe.JSON_parse(innerResponse); } catch { } - } - if ( typeof obj !== 'object' || obj === null ) { - return (xhrDetails.response = innerResponse); - } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { - return (xhrDetails.response = innerResponse); - } - safe.uboLog(logPrefix, 'Edited'); - const outerResponse = typeof innerResponse === 'string' - ? JSONPath.toJSON(objAfter, safe.JSON_stringify) - : objAfter; - return (xhrDetails.response = outerResponse); + const editObj = objBefore => { + const objAfter = jsonp.apply(objBefore); + if ( objAfter === undefined ) { return; } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel <= 1 ) { return; } + safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + }; + proxyApplyFn(propChain, function(context) { + const { thisArg } = context; + let r; + if ( order === 'after' ) { + r = context.reflect(); } - get responseText() { - const response = this.response; - return typeof response !== 'string' - ? super.responseText - : response; + editObj(thisArg); + if ( order !== 'after' ) { + r = context.reflect(); } - }; + return r; + }); } -function jsonEditXhrResponse(jsonq = '', ...args) { - jsonEditXhrResponseFn(false, jsonq, ...args); +function editThisObject(...args) { + editThisObjectFn(false, ...args); }; -jsonEditXhrResponse(...args); +editThisObject(...args); }, }; -scriptlets['trusted-json-edit-xhr-response.js'] = { +scriptlets['trusted-edit-this-object.js'] = { aliases: [], requiresTrust: true, func: function (scriptletGlobals = {}, ...args) { -function parsePropertiesToMatchFn(propsToMatch, implicit = '') { - const safe = safeSelf(); - const needles = new Map(); - if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } - const options = { canNegate: true }; - for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { - let [ prop, pattern ] = safe.String_split.call(needle, ':'); - if ( prop === '' ) { continue; } - if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { - prop = `${prop}:${pattern}`; - pattern = undefined; - } - if ( pattern !== undefined ) { - needles.set(prop, safe.initPattern(pattern, options)); - } else if ( implicit !== '' ) { - needles.set(implicit, safe.initPattern(prop, options)); - } - } - return needles; -} function safeSelf() { if ( scriptletGlobals.safeSelf ) { return scriptletGlobals.safeSelf; @@ -7350,24 +7356,100 @@ function safeSelf() { } return safe; } -function matchObjectPropertiesFn(propNeedles, ...objs) { - const safe = safeSelf(); - const matched = []; - for ( const obj of objs ) { - if ( obj instanceof Object === false ) { continue; } - for ( const [ prop, details ] of propNeedles ) { - let value = obj[prop]; - if ( value === undefined ) { continue; } - if ( typeof value !== 'string' ) { - try { value = safe.JSON_stringify(value); } - catch { } - if ( typeof value !== 'string' ) { continue; } +function proxyApplyFn( + target = '', + handler = '' +) { + let context = globalThis; + let prop = target; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); + } + const fn = context[prop]; + if ( typeof fn !== 'function' ) { return; } + if ( proxyApplyFn.CtorContext === undefined ) { + proxyApplyFn.ctorContexts = []; + proxyApplyFn.CtorContext = class { + constructor(...args) { + this.init(...args); } - if ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); + } + }; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); + } + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs); + this.callFn = this.thisArg = this.callArgs = this.private = undefined; + proxyApplyFn.applyContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); + } + }; + proxyApplyFn.isCtor = new Map(); + proxyApplyFn.proxies = new WeakMap(); + proxyApplyFn.nativeToString = Function.prototype.toString; + const proxiedToString = new Proxy(Function.prototype.toString, { + apply(target, thisArg) { + let proxied = thisArg; + for(;;) { + const fn = proxyApplyFn.proxies.get(proxied); + if ( fn === undefined ) { break; } + proxied = fn; + } + return proxyApplyFn.nativeToString.call(proxied); + } + }); + proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); + Function.prototype.toString = proxiedToString; + } + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); + } + const proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); } + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; } - return matched; + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; } class JSONPath { static create(query) { @@ -7413,7 +7495,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -7819,82 +7901,52 @@ class JSONPath { } } } -function jsonEditXhrResponseFn(trusted, jsonq = '') { +function editThisObjectFn( + trusted = false, + propChain = '', + jsonq = '', + order = '' +) { + if ( propChain === '' ) { return; } const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-xhr-response`, + `${trusted ? 'trusted-' : ''}edit-this-object`, + propChain, jsonq ); - const xhrInstances = new WeakMap(); const jsonp = JSONPath.create(jsonq); if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { return safe.uboLog(logPrefix, 'Bad JSONPath query'); } - const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - self.XMLHttpRequest = class extends self.XMLHttpRequest { - open(method, url, ...args) { - const xhrDetails = { method, url }; - const matched = propNeedles.size === 0 || - matchObjectPropertiesFn(propNeedles, xhrDetails); - if ( matched ) { - if ( safe.logLevel > 1 && Array.isArray(matched) ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - xhrInstances.set(this, xhrDetails); - } - return super.open(method, url, ...args); + const editObj = objBefore => { + const objAfter = jsonp.apply(objBefore); + if ( objAfter === undefined ) { return; } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel <= 1 ) { return; } + safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + }; + proxyApplyFn(propChain, function(context) { + const { thisArg } = context; + let r; + if ( order === 'after' ) { + r = context.reflect(); } - get response() { - const innerResponse = super.response; - const xhrDetails = xhrInstances.get(this); - if ( xhrDetails === undefined ) { return innerResponse; } - const responseLength = typeof innerResponse === 'string' - ? innerResponse.length - : undefined; - if ( xhrDetails.lastResponseLength !== responseLength ) { - xhrDetails.response = undefined; - xhrDetails.lastResponseLength = responseLength; - } - if ( xhrDetails.response !== undefined ) { - return xhrDetails.response; - } - let obj; - if ( typeof innerResponse === 'object' ) { - obj = innerResponse; - } else if ( typeof innerResponse === 'string' ) { - try { obj = safe.JSON_parse(innerResponse); } catch { } - } - if ( typeof obj !== 'object' || obj === null ) { - return (xhrDetails.response = innerResponse); - } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { - return (xhrDetails.response = innerResponse); - } - safe.uboLog(logPrefix, 'Edited'); - const outerResponse = typeof innerResponse === 'string' - ? JSONPath.toJSON(objAfter, safe.JSON_stringify) - : objAfter; - return (xhrDetails.response = outerResponse); + editObj(thisArg); + if ( order !== 'after' ) { + r = context.reflect(); } - get responseText() { - const response = this.response; - return typeof response !== 'string' - ? super.responseText - : response; - } - }; + return r; + }); } -function trustedJsonEditXhrResponse(jsonq = '', ...args) { - jsonEditXhrResponseFn(true, jsonq, ...args); +function trustedEditThisObject(...args) { + editThisObjectFn(true, ...args); }; -trustedJsonEditXhrResponse(...args); +trustedEditThisObject(...args); }, }; -scriptlets['json-edit-xhr-request.js'] = { +scriptlets['json-edit-xhr-response.js'] = { aliases: [], requiresTrust: false, @@ -8172,7 +8224,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -8578,10 +8630,10 @@ class JSONPath { } } } -function jsonEditXhrRequestFn(trusted, jsonq = '') { +function jsonEditXhrResponseFn(trusted, jsonq = '') { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-xhr-request`, + `${trusted ? 'trusted-' : ''}json-edit-xhr-response`, jsonq ); const xhrInstances = new WeakMap(); @@ -8604,39 +8656,56 @@ function jsonEditXhrRequestFn(trusted, jsonq = '') { } return super.open(method, url, ...args); } - send(body) { + get response() { + const innerResponse = super.response; const xhrDetails = xhrInstances.get(this); - if ( xhrDetails ) { - body = this.#filterBody(body) || body; + if ( xhrDetails === undefined ) { return innerResponse; } + const responseLength = typeof innerResponse === 'string' + ? innerResponse.length + : undefined; + if ( xhrDetails.lastResponseLength !== responseLength ) { + xhrDetails.response = undefined; + xhrDetails.lastResponseLength = responseLength; } - super.send(body); - } - #filterBody(body) { - if ( typeof body !== 'string' ) { return; } - let data; - try { data = safe.JSON_parse(body); } - catch { } - if ( data instanceof Object === false ) { return; } - const objAfter = jsonp.apply(data); - if ( objAfter === undefined ) { return; } - body = safe.JSON_stringify(objAfter); - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${body}`); + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; } - return body; + let obj; + if ( typeof innerResponse === 'object' ) { + obj = innerResponse; + } else if ( typeof innerResponse === 'string' ) { + try { obj = safe.JSON_parse(innerResponse); } catch { } + } + if ( typeof obj !== 'object' || obj === null ) { + return (xhrDetails.response = innerResponse); + } + const objAfter = jsonp.apply(obj); + if ( objAfter === undefined ) { + return (xhrDetails.response = innerResponse); + } + safe.uboLog(logPrefix, 'Edited'); + const outerResponse = typeof innerResponse === 'string' + ? JSONPath.toJSON(objAfter, safe.JSON_stringify) + : objAfter; + return (xhrDetails.response = outerResponse); + } + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; } }; } -function jsonEditXhrRequest(jsonq = '', ...args) { - jsonEditXhrRequestFn(false, jsonq, ...args); +function jsonEditXhrResponse(jsonq = '', ...args) { + jsonEditXhrResponseFn(false, jsonq, ...args); }; -jsonEditXhrRequest(...args); +jsonEditXhrResponse(...args); }, }; -scriptlets['trusted-json-edit-xhr-request.js'] = { +scriptlets['trusted-json-edit-xhr-response.js'] = { aliases: [], requiresTrust: true, @@ -8914,7 +8983,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -9320,10 +9389,10 @@ class JSONPath { } } } -function jsonEditXhrRequestFn(trusted, jsonq = '') { +function jsonEditXhrResponseFn(trusted, jsonq = '') { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-xhr-request`, + `${trusted ? 'trusted-' : ''}json-edit-xhr-response`, jsonq ); const xhrInstances = new WeakMap(); @@ -9346,138 +9415,60 @@ function jsonEditXhrRequestFn(trusted, jsonq = '') { } return super.open(method, url, ...args); } - send(body) { + get response() { + const innerResponse = super.response; const xhrDetails = xhrInstances.get(this); - if ( xhrDetails ) { - body = this.#filterBody(body) || body; + if ( xhrDetails === undefined ) { return innerResponse; } + const responseLength = typeof innerResponse === 'string' + ? innerResponse.length + : undefined; + if ( xhrDetails.lastResponseLength !== responseLength ) { + xhrDetails.response = undefined; + xhrDetails.lastResponseLength = responseLength; } - super.send(body); - } - #filterBody(body) { - if ( typeof body !== 'string' ) { return; } - let data; - try { data = safe.JSON_parse(body); } - catch { } - if ( data instanceof Object === false ) { return; } - const objAfter = jsonp.apply(data); - if ( objAfter === undefined ) { return; } - body = safe.JSON_stringify(objAfter); - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${body}`); + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; } - return body; + let obj; + if ( typeof innerResponse === 'object' ) { + obj = innerResponse; + } else if ( typeof innerResponse === 'string' ) { + try { obj = safe.JSON_parse(innerResponse); } catch { } + } + if ( typeof obj !== 'object' || obj === null ) { + return (xhrDetails.response = innerResponse); + } + const objAfter = jsonp.apply(obj); + if ( objAfter === undefined ) { + return (xhrDetails.response = innerResponse); + } + safe.uboLog(logPrefix, 'Edited'); + const outerResponse = typeof innerResponse === 'string' + ? JSONPath.toJSON(objAfter, safe.JSON_stringify) + : objAfter; + return (xhrDetails.response = outerResponse); + } + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; } }; } -function trustedJsonEditXhrRequest(jsonq = '', ...args) { - jsonEditXhrRequestFn(true, jsonq, ...args); +function trustedJsonEditXhrResponse(jsonq = '', ...args) { + jsonEditXhrResponseFn(true, jsonq, ...args); }; -trustedJsonEditXhrRequest(...args); +trustedJsonEditXhrResponse(...args); }, }; -scriptlets['json-edit-fetch-response.js'] = { +scriptlets['json-edit-xhr-request.js'] = { aliases: [], requiresTrust: false, func: function (scriptletGlobals = {}, ...args) { -function proxyApplyFn( - target = '', - handler = '' -) { - let context = globalThis; - let prop = target; - for (;;) { - const pos = prop.indexOf('.'); - if ( pos === -1 ) { break; } - context = context[prop.slice(0, pos)]; - if ( context instanceof Object === false ) { return; } - prop = prop.slice(pos+1); - } - const fn = context[prop]; - if ( typeof fn !== 'function' ) { return; } - if ( 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(); - 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(); @@ -9498,6 +9489,196 @@ function parsePropertiesToMatchFn(propsToMatch, implicit = '') { } return needles; } +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + 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 /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + onIdle(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); + }, + offIdle(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); + } + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} function matchObjectPropertiesFn(propNeedles, ...objs) { const safe = safeSelf(); const matched = []; @@ -9561,7 +9742,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -9967,6 +10148,89 @@ class JSONPath { } } } +function jsonEditXhrRequestFn(trusted, jsonq = '') { + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}json-edit-xhr-request`, + jsonq + ); + const xhrInstances = new WeakMap(); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); + const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); + self.XMLHttpRequest = class extends self.XMLHttpRequest { + open(method, url, ...args) { + const xhrDetails = { method, url }; + const matched = propNeedles.size === 0 || + matchObjectPropertiesFn(propNeedles, xhrDetails); + if ( matched ) { + if ( safe.logLevel > 1 && Array.isArray(matched) ) { + safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); + } + xhrInstances.set(this, xhrDetails); + } + return super.open(method, url, ...args); + } + send(body) { + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails ) { + body = this.#filterBody(body) || body; + } + super.send(body); + } + #filterBody(body) { + if ( typeof body !== 'string' ) { return; } + let data; + try { data = safe.JSON_parse(body); } + catch { } + if ( data instanceof Object === false ) { return; } + const objAfter = jsonp.apply(data); + if ( objAfter === undefined ) { return; } + body = safe.JSON_stringify(objAfter); + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `After edit:\n${body}`); + } + return body; + } + }; +} +function jsonEditXhrRequest(jsonq = '', ...args) { + jsonEditXhrRequestFn(false, jsonq, ...args); +}; +jsonEditXhrRequest(...args); +}, +}; + + +scriptlets['trusted-json-edit-xhr-request.js'] = { +aliases: [], + +requiresTrust: true, +func: function (scriptletGlobals = {}, ...args) { +function parsePropertiesToMatchFn(propsToMatch, implicit = '') { + const safe = safeSelf(); + const needles = new Map(); + if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } + const options = { canNegate: true }; + for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { + let [ prop, pattern ] = safe.String_split.call(needle, ':'); + if ( prop === '' ) { continue; } + if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { + prop = `${prop}:${pattern}`; + pattern = undefined; + } + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); + } + } + return needles; +} function safeSelf() { if ( scriptletGlobals.safeSelf ) { return scriptletGlobals.safeSelf; @@ -10157,220 +10421,6 @@ function safeSelf() { } return safe; } -function collateFetchArgumentsFn(resource, options) { - const safe = safeSelf(); - const props = [ - 'body', 'cache', 'credentials', 'duplex', 'headers', - 'integrity', 'keepalive', 'method', 'mode', 'priority', - 'redirect', 'referrer', 'referrerPolicy', 'url' - ]; - const out = {}; - if ( collateFetchArgumentsFn.collateKnownProps === undefined ) { - collateFetchArgumentsFn.collateKnownProps = (src, out) => { - for ( const prop of props ) { - if ( src[prop] === undefined ) { continue; } - out[prop] = src[prop]; - } - }; - } - if ( - typeof resource !== 'object' || - safe.Object_toString.call(resource) !== '[object Request]' - ) { - out.url = `${resource}`; - } else { - let clone; - try { - clone = safe.Request_clone.call(resource); - } catch { - } - collateFetchArgumentsFn.collateKnownProps(clone || resource, out); - } - if ( typeof options === 'object' && options !== null ) { - collateFetchArgumentsFn.collateKnownProps(options, out); - } - return out; -} -function jsonEditFetchResponseFn(trusted, jsonq = '') { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-fetch-response`, - jsonq - ); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - proxyApplyFn('fetch', function(context) { - const args = context.callArgs; - const fetchPromise = context.reflect(); - if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(...args); - const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return fetchPromise; } - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - } - return fetchPromise.then(responseBefore => { - const response = responseBefore.clone(); - return response.json().then(obj => { - if ( typeof obj !== 'object' ) { return responseBefore; } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { return responseBefore; } - safe.uboLog(logPrefix, 'Edited'); - const responseAfter = Response.json(objAfter, { - status: responseBefore.status, - statusText: responseBefore.statusText, - headers: responseBefore.headers, - }); - Object.defineProperties(responseAfter, { - ok: { value: responseBefore.ok }, - redirected: { value: responseBefore.redirected }, - type: { value: responseBefore.type }, - url: { value: responseBefore.url }, - }); - return responseAfter; - }).catch(reason => { - safe.uboErr(logPrefix, 'Error:', reason); - return responseBefore; - }); - }).catch(reason => { - safe.uboErr(logPrefix, 'Error:', reason); - return fetchPromise; - }); - }); -} -function jsonEditFetchResponse(jsonq = '', ...args) { - jsonEditFetchResponseFn(false, jsonq, ...args); -}; -jsonEditFetchResponse(...args); -}, -}; - - -scriptlets['trusted-json-edit-fetch-response.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -function proxyApplyFn( - target = '', - handler = '' -) { - let context = globalThis; - let prop = target; - for (;;) { - const pos = prop.indexOf('.'); - if ( pos === -1 ) { break; } - context = context[prop.slice(0, pos)]; - if ( context instanceof Object === false ) { return; } - prop = prop.slice(pos+1); - } - const fn = context[prop]; - if ( typeof fn !== 'function' ) { return; } - if ( 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(); - 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 = []; @@ -10434,7 +10484,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -10840,291 +10890,65 @@ class JSONPath { } } } -function safeSelf() { - if ( scriptletGlobals.safeSelf ) { - return scriptletGlobals.safeSelf; - } - 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 /^/; - }, - getExtraArgs(args, offset = 0) { - const entries = args.slice(offset).reduce((out, v, i, a) => { - if ( (i & 1) === 0 ) { - const rawValue = a[i+1]; - const value = /^\d+$/.test(rawValue) - ? parseInt(rawValue, 10) - : rawValue; - out.push([ a[i], value ]); - } - return out; - }, []); - return this.Object_fromEntries(entries); - }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } - }; - scriptletGlobals.safeSelf = safe; - if ( scriptletGlobals.bcSecret === undefined ) { return safe; } - // This is executed only when the logger is opened - safe.logLevel = scriptletGlobals.logLevel || 1; - let lastLogType = ''; - let lastLogText = ''; - let lastLogTime = 0; - safe.toLogText = (type, ...args) => { - if ( args.length === 0 ) { return; } - const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; - if ( text === lastLogText && type === lastLogType ) { - if ( (Date.now() - lastLogTime) < 5000 ) { return; } - } - lastLogType = type; - lastLogText = text; - lastLogTime = Date.now(); - return text; - }; - try { - const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); - let bcBuffer = []; - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - if ( bcBuffer === undefined ) { - return bc.postMessage({ what: 'messageToLogger', type, text }); - } - bcBuffer.push({ type, text }); - }; - bc.onmessage = ev => { - const msg = ev.data; - switch ( msg ) { - case 'iamready!': - if ( bcBuffer === undefined ) { break; } - bcBuffer.forEach(({ type, text }) => - bc.postMessage({ what: 'messageToLogger', type, text }) - ); - bcBuffer = undefined; - break; - case 'setScriptletLogLevelToOne': - safe.logLevel = 1; - break; - case 'setScriptletLogLevelToTwo': - safe.logLevel = 2; - break; - } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; -} -function collateFetchArgumentsFn(resource, options) { - const safe = safeSelf(); - const props = [ - 'body', 'cache', 'credentials', 'duplex', 'headers', - 'integrity', 'keepalive', 'method', 'mode', 'priority', - 'redirect', 'referrer', 'referrerPolicy', 'url' - ]; - const out = {}; - if ( collateFetchArgumentsFn.collateKnownProps === undefined ) { - collateFetchArgumentsFn.collateKnownProps = (src, out) => { - for ( const prop of props ) { - if ( src[prop] === undefined ) { continue; } - out[prop] = src[prop]; - } - }; - } - if ( - typeof resource !== 'object' || - safe.Object_toString.call(resource) !== '[object Request]' - ) { - out.url = `${resource}`; - } else { - let clone; - try { - clone = safe.Request_clone.call(resource); - } catch { - } - collateFetchArgumentsFn.collateKnownProps(clone || resource, out); - } - if ( typeof options === 'object' && options !== null ) { - collateFetchArgumentsFn.collateKnownProps(options, out); - } - return out; -} -function jsonEditFetchResponseFn(trusted, jsonq = '') { +function jsonEditXhrRequestFn(trusted, jsonq = '') { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-fetch-response`, + `${trusted ? 'trusted-' : ''}json-edit-xhr-request`, jsonq ); + const xhrInstances = new WeakMap(); const jsonp = JSONPath.create(jsonq); if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { return safe.uboLog(logPrefix, 'Bad JSONPath query'); } const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - proxyApplyFn('fetch', function(context) { - const args = context.callArgs; - const fetchPromise = context.reflect(); - if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(...args); - const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return fetchPromise; } + self.XMLHttpRequest = class extends self.XMLHttpRequest { + open(method, url, ...args) { + const xhrDetails = { method, url }; + const matched = propNeedles.size === 0 || + matchObjectPropertiesFn(propNeedles, xhrDetails); + if ( matched ) { + if ( safe.logLevel > 1 && Array.isArray(matched) ) { + safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); + } + xhrInstances.set(this, xhrDetails); + } + return super.open(method, url, ...args); + } + send(body) { + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails ) { + body = this.#filterBody(body) || body; + } + super.send(body); + } + #filterBody(body) { + if ( typeof body !== 'string' ) { return; } + let data; + try { data = safe.JSON_parse(body); } + catch { } + if ( data instanceof Object === false ) { return; } + const objAfter = jsonp.apply(data); + if ( objAfter === undefined ) { return; } + body = safe.JSON_stringify(objAfter); + safe.uboLog(logPrefix, 'Edited'); if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); + safe.uboLog(logPrefix, `After edit:\n${body}`); } + return body; } - return fetchPromise.then(responseBefore => { - const response = responseBefore.clone(); - return response.json().then(obj => { - if ( typeof obj !== 'object' ) { return responseBefore; } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { return responseBefore; } - safe.uboLog(logPrefix, 'Edited'); - const responseAfter = Response.json(objAfter, { - status: responseBefore.status, - statusText: responseBefore.statusText, - headers: responseBefore.headers, - }); - Object.defineProperties(responseAfter, { - ok: { value: responseBefore.ok }, - redirected: { value: responseBefore.redirected }, - type: { value: responseBefore.type }, - url: { value: responseBefore.url }, - }); - return responseAfter; - }).catch(reason => { - safe.uboErr(logPrefix, 'Error:', reason); - return responseBefore; - }); - }).catch(reason => { - safe.uboErr(logPrefix, 'Error:', reason); - return fetchPromise; - }); - }); + }; } -function trustedJsonEditFetchResponse(jsonq = '', ...args) { - jsonEditFetchResponseFn(true, jsonq, ...args); +function trustedJsonEditXhrRequest(jsonq = '', ...args) { + jsonEditXhrRequestFn(true, jsonq, ...args); }; -trustedJsonEditFetchResponse(...args); +trustedJsonEditXhrRequest(...args); }, }; -scriptlets['json-edit-fetch-request.js'] = { +scriptlets['json-edit-fetch-response.js'] = { aliases: [], requiresTrust: false, @@ -11307,7 +11131,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -11937,10 +11761,10 @@ function collateFetchArgumentsFn(resource, options) { } return out; } -function jsonEditFetchRequestFn(trusted, jsonq = '') { +function jsonEditFetchResponseFn(trusted, jsonq = '') { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-fetch-request`, + `${trusted ? 'trusted-' : ''}json-edit-fetch-response`, jsonq ); const jsonp = JSONPath.create(jsonq); @@ -11949,52 +11773,55 @@ function jsonEditFetchRequestFn(trusted, jsonq = '') { } const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - const filterBody = body => { - if ( typeof body !== 'string' ) { return; } - let data; - try { data = safe.JSON_parse(body); } - catch { } - if ( data instanceof Object === false ) { return; } - const objAfter = jsonp.apply(data); - if ( objAfter === undefined ) { return; } - return safe.JSON_stringify(objAfter); - } - const proxyHandler = context => { + proxyApplyFn('fetch', function(context) { const args = context.callArgs; - const [ resource, options ] = args; - const bodyBefore = options?.body; - if ( Boolean(bodyBefore) === false ) { return context.reflect(); } - const bodyAfter = filterBody(bodyBefore); - if ( bodyAfter === undefined || bodyAfter === bodyBefore ) { - return context.reflect(); - } + const fetchPromise = context.reflect(); if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(resource, options); + const props = collateFetchArgumentsFn(...args); const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return context.reflect(); } + if ( matched === undefined ) { return fetchPromise; } if ( safe.logLevel > 1 ) { safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); } } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${bodyAfter}`); - } - options.body = bodyAfter; - return context.reflect(); - }; - proxyApplyFn('fetch', proxyHandler); - proxyApplyFn('Request', proxyHandler); + return fetchPromise.then(responseBefore => { + const response = responseBefore.clone(); + return response.json().then(obj => { + if ( typeof obj !== 'object' ) { return responseBefore; } + const objAfter = jsonp.apply(obj); + if ( objAfter === undefined ) { return responseBefore; } + safe.uboLog(logPrefix, 'Edited'); + const responseAfter = Response.json(objAfter, { + status: responseBefore.status, + statusText: responseBefore.statusText, + headers: responseBefore.headers, + }); + Object.defineProperties(responseAfter, { + ok: { value: responseBefore.ok }, + redirected: { value: responseBefore.redirected }, + type: { value: responseBefore.type }, + url: { value: responseBefore.url }, + }); + return responseAfter; + }).catch(reason => { + safe.uboErr(logPrefix, 'Error:', reason); + return responseBefore; + }); + }).catch(reason => { + safe.uboErr(logPrefix, 'Error:', reason); + return fetchPromise; + }); + }); } -function jsonEditFetchRequest(jsonq = '', ...args) { - jsonEditFetchRequestFn(false, jsonq, ...args); +function jsonEditFetchResponse(jsonq = '', ...args) { + jsonEditFetchResponseFn(false, jsonq, ...args); }; -jsonEditFetchRequest(...args); +jsonEditFetchResponse(...args); }, }; -scriptlets['trusted-json-edit-fetch-request.js'] = { +scriptlets['trusted-json-edit-fetch-response.js'] = { aliases: [], requiresTrust: true, @@ -12177,7 +12004,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -12807,10 +12634,10 @@ function collateFetchArgumentsFn(resource, options) { } return out; } -function jsonEditFetchRequestFn(trusted, jsonq = '') { +function jsonEditFetchResponseFn(trusted, jsonq = '') { const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}json-edit-fetch-request`, + `${trusted ? 'trusted-' : ''}json-edit-fetch-response`, jsonq ); const jsonp = JSONPath.create(jsonq); @@ -12819,56 +12646,154 @@ function jsonEditFetchRequestFn(trusted, jsonq = '') { } const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - const filterBody = body => { - if ( typeof body !== 'string' ) { return; } - let data; - try { data = safe.JSON_parse(body); } - catch { } - if ( data instanceof Object === false ) { return; } - const objAfter = jsonp.apply(data); - if ( objAfter === undefined ) { return; } - return safe.JSON_stringify(objAfter); - } - const proxyHandler = context => { + proxyApplyFn('fetch', function(context) { const args = context.callArgs; - const [ resource, options ] = args; - const bodyBefore = options?.body; - if ( Boolean(bodyBefore) === false ) { return context.reflect(); } - const bodyAfter = filterBody(bodyBefore); - if ( bodyAfter === undefined || bodyAfter === bodyBefore ) { - return context.reflect(); - } + const fetchPromise = context.reflect(); if ( propNeedles.size !== 0 ) { - const props = collateFetchArgumentsFn(resource, options); + const props = collateFetchArgumentsFn(...args); const matched = matchObjectPropertiesFn(propNeedles, props); - if ( matched === undefined ) { return context.reflect(); } + if ( matched === undefined ) { return fetchPromise; } if ( safe.logLevel > 1 ) { safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); } } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${bodyAfter}`); - } - options.body = bodyAfter; - return context.reflect(); - }; - proxyApplyFn('fetch', proxyHandler); - proxyApplyFn('Request', proxyHandler); + return fetchPromise.then(responseBefore => { + const response = responseBefore.clone(); + return response.json().then(obj => { + if ( typeof obj !== 'object' ) { return responseBefore; } + const objAfter = jsonp.apply(obj); + if ( objAfter === undefined ) { return responseBefore; } + safe.uboLog(logPrefix, 'Edited'); + const responseAfter = Response.json(objAfter, { + status: responseBefore.status, + statusText: responseBefore.statusText, + headers: responseBefore.headers, + }); + Object.defineProperties(responseAfter, { + ok: { value: responseBefore.ok }, + redirected: { value: responseBefore.redirected }, + type: { value: responseBefore.type }, + url: { value: responseBefore.url }, + }); + return responseAfter; + }).catch(reason => { + safe.uboErr(logPrefix, 'Error:', reason); + return responseBefore; + }); + }).catch(reason => { + safe.uboErr(logPrefix, 'Error:', reason); + return fetchPromise; + }); + }); } -function trustedJsonEditFetchRequest(jsonq = '', ...args) { - jsonEditFetchRequestFn(true, jsonq, ...args); +function trustedJsonEditFetchResponse(jsonq = '', ...args) { + jsonEditFetchResponseFn(true, jsonq, ...args); }; -trustedJsonEditFetchRequest(...args); +trustedJsonEditFetchResponse(...args); }, }; -scriptlets['jsonl-edit-xhr-response.js'] = { +scriptlets['json-edit-fetch-request.js'] = { aliases: [], requiresTrust: false, func: function (scriptletGlobals = {}, ...args) { +function proxyApplyFn( + target = '', + handler = '' +) { + let context = globalThis; + let prop = target; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); + } + const fn = context[prop]; + if ( typeof fn !== 'function' ) { return; } + if ( 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(); + 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(); @@ -12908,218 +12833,6 @@ function matchObjectPropertiesFn(propNeedles, ...objs) { } return matched; } -function safeSelf() { - if ( scriptletGlobals.safeSelf ) { - return scriptletGlobals.safeSelf; - } - 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 /^/; - }, - getExtraArgs(args, offset = 0) { - const entries = args.slice(offset).reduce((out, v, i, a) => { - if ( (i & 1) === 0 ) { - const rawValue = a[i+1]; - const value = /^\d+$/.test(rawValue) - ? parseInt(rawValue, 10) - : rawValue; - out.push([ a[i], value ]); - } - return out; - }, []); - return this.Object_fromEntries(entries); - }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } - }; - scriptletGlobals.safeSelf = safe; - if ( scriptletGlobals.bcSecret === undefined ) { return safe; } - // This is executed only when the logger is opened - safe.logLevel = scriptletGlobals.logLevel || 1; - let lastLogType = ''; - let lastLogText = ''; - let lastLogTime = 0; - safe.toLogText = (type, ...args) => { - if ( args.length === 0 ) { return; } - const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; - if ( text === lastLogText && type === lastLogType ) { - if ( (Date.now() - lastLogTime) < 5000 ) { return; } - } - lastLogType = type; - lastLogText = text; - lastLogTime = Date.now(); - return text; - }; - try { - const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); - let bcBuffer = []; - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - if ( bcBuffer === undefined ) { - return bc.postMessage({ what: 'messageToLogger', type, text }); - } - bcBuffer.push({ type, text }); - }; - bc.onmessage = ev => { - const msg = ev.data; - switch ( msg ) { - case 'iamready!': - if ( bcBuffer === undefined ) { break; } - bcBuffer.forEach(({ type, text }) => - bc.postMessage({ what: 'messageToLogger', type, text }) - ); - bcBuffer = undefined; - break; - case 'setScriptletLogLevelToOne': - safe.logLevel = 1; - break; - case 'setScriptletLogLevelToTwo': - safe.logLevel = 2; - break; - } - }; - bc.postMessage('areyouready?'); - } catch { - safe.sendToLogger = (type, ...args) => { - const text = safe.toLogText(type, ...args); - if ( text === undefined ) { return; } - safe.log(`uBO ${text}`); - }; - } - return safe; -} -function jsonlEditFn(jsonp, text = '') { - const safe = safeSelf(); - const lineSeparator = /\r?\n/.exec(text)?.[0] || '\n'; - const linesBefore = text.split('\n'); - const linesAfter = []; - for ( const lineBefore of linesBefore ) { - let obj; - try { obj = safe.JSON_parse(lineBefore); } catch { } - if ( typeof obj !== 'object' || obj === null ) { - linesAfter.push(lineBefore); - continue; - } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { - linesAfter.push(lineBefore); - continue; - } - const lineAfter = safe.JSON_stringify(objAfter); - linesAfter.push(lineAfter); - } - return linesAfter.join(lineSeparator); -} class JSONPath { static create(query) { const jsonp = new JSONPath(); @@ -13164,7 +12877,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -13570,117 +13283,6 @@ class JSONPath { } } } -function jsonlEditXhrResponseFn(trusted, jsonq = '') { - const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}jsonl-edit-xhr-response`, - jsonq - ); - const xhrInstances = new WeakMap(); - const jsonp = JSONPath.create(jsonq); - if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { - return safe.uboLog(logPrefix, 'Bad JSONPath query'); - } - const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); - const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - self.XMLHttpRequest = class extends self.XMLHttpRequest { - open(method, url, ...args) { - const xhrDetails = { method, url }; - const matched = propNeedles.size === 0 || - matchObjectPropertiesFn(propNeedles, xhrDetails); - if ( matched ) { - if ( safe.logLevel > 1 && Array.isArray(matched) ) { - safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); - } - xhrInstances.set(this, xhrDetails); - } - return super.open(method, url, ...args); - } - get response() { - const innerResponse = super.response; - const xhrDetails = xhrInstances.get(this); - if ( xhrDetails === undefined ) { - return innerResponse; - } - const responseLength = typeof innerResponse === 'string' - ? innerResponse.length - : undefined; - if ( xhrDetails.lastResponseLength !== responseLength ) { - xhrDetails.response = undefined; - xhrDetails.lastResponseLength = responseLength; - } - if ( xhrDetails.response !== undefined ) { - return xhrDetails.response; - } - if ( typeof innerResponse !== 'string' ) { - return (xhrDetails.response = innerResponse); - } - const outerResponse = jsonlEditFn(jsonp, innerResponse); - if ( outerResponse !== innerResponse ) { - safe.uboLog(logPrefix, 'Pruned'); - } - return (xhrDetails.response = outerResponse); - } - get responseText() { - const response = this.response; - return typeof response !== 'string' - ? super.responseText - : response; - } - }; -} -function jsonlEditXhrResponse(jsonq = '', ...args) { - jsonlEditXhrResponseFn(false, jsonq, ...args); -}; -jsonlEditXhrResponse(...args); -}, -}; - - -scriptlets['trusted-jsonl-edit-xhr-response.js'] = { -aliases: [], - -requiresTrust: true, -func: function (scriptletGlobals = {}, ...args) { -function parsePropertiesToMatchFn(propsToMatch, implicit = '') { - const safe = safeSelf(); - const needles = new Map(); - if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } - const options = { canNegate: true }; - for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { - let [ prop, pattern ] = safe.String_split.call(needle, ':'); - if ( prop === '' ) { continue; } - if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { - prop = `${prop}:${pattern}`; - pattern = undefined; - } - if ( pattern !== undefined ) { - needles.set(prop, safe.initPattern(pattern, options)); - } else if ( implicit !== '' ) { - needles.set(implicit, safe.initPattern(prop, options)); - } - } - return needles; -} -function matchObjectPropertiesFn(propNeedles, ...objs) { - const safe = safeSelf(); - const matched = []; - for ( const obj of objs ) { - if ( obj instanceof Object === false ) { continue; } - for ( const [ prop, details ] of propNeedles ) { - let value = obj[prop]; - if ( value === undefined ) { continue; } - if ( typeof value !== 'string' ) { - try { value = safe.JSON_stringify(value); } - catch { } - if ( typeof value !== 'string' ) { continue; } - } - if ( safe.testPattern(details, value) === false ) { return; } - matched.push(`${prop}: ${value}`); - } - } - return matched; -} function safeSelf() { if ( scriptletGlobals.safeSelf ) { return scriptletGlobals.safeSelf; @@ -13871,27 +13473,235 @@ function safeSelf() { } return safe; } -function jsonlEditFn(jsonp, text = '') { +function collateFetchArgumentsFn(resource, options) { const safe = safeSelf(); - const lineSeparator = /\r?\n/.exec(text)?.[0] || '\n'; - const linesBefore = text.split('\n'); - const linesAfter = []; - for ( const lineBefore of linesBefore ) { - let obj; - try { obj = safe.JSON_parse(lineBefore); } catch { } - if ( typeof obj !== 'object' || obj === null ) { - linesAfter.push(lineBefore); - continue; + const props = [ + 'body', 'cache', 'credentials', 'duplex', 'headers', + 'integrity', 'keepalive', 'method', 'mode', 'priority', + 'redirect', 'referrer', 'referrerPolicy', 'url' + ]; + const out = {}; + if ( collateFetchArgumentsFn.collateKnownProps === undefined ) { + collateFetchArgumentsFn.collateKnownProps = (src, out) => { + for ( const prop of props ) { + if ( src[prop] === undefined ) { continue; } + out[prop] = src[prop]; + } + }; + } + if ( + typeof resource !== 'object' || + safe.Object_toString.call(resource) !== '[object Request]' + ) { + out.url = `${resource}`; + } else { + let clone; + try { + clone = safe.Request_clone.call(resource); + } catch { } - const objAfter = jsonp.apply(obj); - if ( objAfter === undefined ) { - linesAfter.push(lineBefore); - continue; + collateFetchArgumentsFn.collateKnownProps(clone || resource, out); + } + if ( typeof options === 'object' && options !== null ) { + collateFetchArgumentsFn.collateKnownProps(options, out); + } + return out; +} +function jsonEditFetchRequestFn(trusted, jsonq = '') { + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}json-edit-fetch-request`, + jsonq + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); + const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); + const filterBody = body => { + if ( typeof body !== 'string' ) { return; } + let data; + try { data = safe.JSON_parse(body); } + catch { } + if ( data instanceof Object === false ) { return; } + const objAfter = jsonp.apply(data); + if ( objAfter === undefined ) { return; } + return safe.JSON_stringify(objAfter); + } + const proxyHandler = context => { + const args = context.callArgs; + const [ resource, options ] = args; + const bodyBefore = options?.body; + if ( Boolean(bodyBefore) === false ) { return context.reflect(); } + const bodyAfter = filterBody(bodyBefore); + if ( bodyAfter === undefined || bodyAfter === bodyBefore ) { + return context.reflect(); } - const lineAfter = safe.JSON_stringify(objAfter); - linesAfter.push(lineAfter); + if ( propNeedles.size !== 0 ) { + const props = collateFetchArgumentsFn(resource, options); + const matched = matchObjectPropertiesFn(propNeedles, props); + if ( matched === undefined ) { return context.reflect(); } + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); + } + } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `After edit:\n${bodyAfter}`); + } + options.body = bodyAfter; + return context.reflect(); + }; + proxyApplyFn('fetch', proxyHandler); + proxyApplyFn('Request', proxyHandler); +} +function jsonEditFetchRequest(jsonq = '', ...args) { + jsonEditFetchRequestFn(false, jsonq, ...args); +}; +jsonEditFetchRequest(...args); +}, +}; + + +scriptlets['trusted-json-edit-fetch-request.js'] = { +aliases: [], + +requiresTrust: true, +func: function (scriptletGlobals = {}, ...args) { +function proxyApplyFn( + target = '', + handler = '' +) { + let context = globalThis; + let prop = target; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); } - return linesAfter.join(lineSeparator); + 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(); + proxyApplyFn.nativeToString = Function.prototype.toString; + const proxiedToString = new Proxy(Function.prototype.toString, { + apply(target, thisArg) { + let proxied = thisArg; + for(;;) { + const fn = proxyApplyFn.proxies.get(proxied); + if ( fn === undefined ) { break; } + proxied = fn; + } + return proxyApplyFn.nativeToString.call(proxied); + } + }); + proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); + Function.prototype.toString = proxiedToString; + } + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); + } + const proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); + } + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; + } + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +function parsePropertiesToMatchFn(propsToMatch, implicit = '') { + const safe = safeSelf(); + const needles = new Map(); + if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } + const options = { canNegate: true }; + for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { + let [ prop, pattern ] = safe.String_split.call(needle, ':'); + if ( prop === '' ) { continue; } + if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { + prop = `${prop}:${pattern}`; + pattern = undefined; + } + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); + } + } + return needles; +} +function matchObjectPropertiesFn(propNeedles, ...objs) { + const safe = safeSelf(); + const matched = []; + for ( const obj of objs ) { + if ( obj instanceof Object === false ) { continue; } + for ( const [ prop, details ] of propNeedles ) { + let value = obj[prop]; + if ( value === undefined ) { continue; } + if ( typeof value !== 'string' ) { + try { value = safe.JSON_stringify(value); } + catch { } + if ( typeof value !== 'string' ) { continue; } + } + if ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); + } + } + return matched; } class JSONPath { static create(query) { @@ -13937,7 +13747,1767 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reIndice = /^-?\d+/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + i = this.#compileExpr(query, step, i); + break; + } + const s = this.#consumeUnquotedIdentifier(query, i); + if ( s === undefined ) { return; } + steps.push({ mv, k: s }); + i += s.length; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */; + const j = i + 2 + (not ? 1 : 0); + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: + resultset = this.#getMatches(resultset, step); + break; + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( step.k === '*' ) { + this.#getMatchesFromAll(pathin, step, owner, listout); + } else if ( step.k !== undefined ) { + this.#getMatchesFromKeys(pathin, step, owner, listout); + } else if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + } + } + return listout; + } + #getMatchesFromAll(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + for ( const { path } of this.#getDescendants(owner, recursive) ) { + out.push([ ...pathin, ...path ]); + } + } + #getMatchesFromKeys(pathin, step, owner, out) { + const kk = Array.isArray(step.k) ? step.k : [ step.k ]; + for ( const k of kk ) { + const normalized = this.#evaluateExpr(step, owner, k); + if ( normalized === undefined ) { continue; } + out.push([ ...pathin, normalized ]); + } + if ( step.mv !== this.#DESCENDANTS ) { return; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + for ( const k of kk ) { + const normalized = this.#evaluateExpr(step, obj[key], k); + if ( normalized === undefined ) { continue; } + out.push([ ...pathin, ...path, normalized ]); + } + } + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + if ( Array.isArray(owner) === false ) { + const r = this.#evaluate(step.steps, pathin); + if ( r.length !== 0 ) { out.push(pathin); } + if ( recursive !== true ) { return; } + } + for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { + if ( Array.isArray(obj[key]) ) { continue; } + const q = [ ...pathin, ...path ]; + const r = this.#evaluate(step.steps, q); + if ( r.length === 0 ) { continue; } + out.push(q); + } + } + #normalizeKey(owner, key) { + if ( typeof key === 'number' ) { + if ( Array.isArray(owner) ) { + return key >= 0 ? key : owner.length + key; + } + } + return key; + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: Object.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ ) { + i += 1; + continue; + } + if ( c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const s = this.#consumeUnquotedIdentifier(query, i); + if ( s === undefined ) { return; } + keys.push(s); + i += s.length; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return match[0]; + } + #untilChar(query, targetCharCode, i) { + const len = query.length; + const parts = []; + let beg = i, end = i; + for (;;) { + if ( end === len ) { return; } + const c = query.charCodeAt(end); + if ( c === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #compileExpr(query, step, i) { + if ( query.startsWith('=/', i) ) { + const r = this.#untilChar(query, 0x2F /* / */, i+2); + if ( r === undefined ) { return i; } + const match = /^[i]/.exec(query.slice(r.i)); + try { + step.rval = new RegExp(r.s, match && match[0] || undefined); + } catch { + return i; + } + step.op = 're'; + if ( match ) { r.i += match[0].length; } + return r.i; + } + const match = this.#reExpr.exec(query.slice(i)); + if ( match === null ) { return i; } + try { + step.rval = JSON.parse(match[2]); + step.op = match[1]; + } catch { + } + return i + match[1].length + match[2].length; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, key) { + if ( owner === undefined || owner === null ) { return; } + if ( typeof key === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + } + const k = this.#normalizeKey(owner, key); + const hasOwn = Object.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + let outcome = false; + switch ( step.op ) { + case '==': outcome = (v === step.rval) === target; break; + case '!=': outcome = (v !== step.rval) === target; break; + case '<': outcome = (v < step.rval) === target; break; + case '<=': outcome = (v <= step.rval) === target; break; + case '>': outcome = (v > step.rval) === target; break; + case '>=': outcome = (v >= step.rval) === target; break; + case '^=': outcome = `${v}`.startsWith(step.rval) === target; break; + case '$=': outcome = `${v}`.endsWith(step.rval) === target; break; + case '*=': outcome = `${v}`.includes(step.rval) === target; break; + case 're': outcome = step.rval.test(`${v}`); break; + default: outcome = hasOwn === target; break; + } + if ( outcome ) { return k; } + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of Object.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new RegExp(rval.regex, rval.flags) + : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { + } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + 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 /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + onIdle(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); + }, + offIdle(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); + } + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +function collateFetchArgumentsFn(resource, options) { + const safe = safeSelf(); + const props = [ + 'body', 'cache', 'credentials', 'duplex', 'headers', + 'integrity', 'keepalive', 'method', 'mode', 'priority', + 'redirect', 'referrer', 'referrerPolicy', 'url' + ]; + const out = {}; + if ( collateFetchArgumentsFn.collateKnownProps === undefined ) { + collateFetchArgumentsFn.collateKnownProps = (src, out) => { + for ( const prop of props ) { + if ( src[prop] === undefined ) { continue; } + out[prop] = src[prop]; + } + }; + } + if ( + typeof resource !== 'object' || + safe.Object_toString.call(resource) !== '[object Request]' + ) { + out.url = `${resource}`; + } else { + let clone; + try { + clone = safe.Request_clone.call(resource); + } catch { + } + collateFetchArgumentsFn.collateKnownProps(clone || resource, out); + } + if ( typeof options === 'object' && options !== null ) { + collateFetchArgumentsFn.collateKnownProps(options, out); + } + return out; +} +function jsonEditFetchRequestFn(trusted, jsonq = '') { + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}json-edit-fetch-request`, + jsonq + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); + const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); + const filterBody = body => { + if ( typeof body !== 'string' ) { return; } + let data; + try { data = safe.JSON_parse(body); } + catch { } + if ( data instanceof Object === false ) { return; } + const objAfter = jsonp.apply(data); + if ( objAfter === undefined ) { return; } + return safe.JSON_stringify(objAfter); + } + const proxyHandler = context => { + const args = context.callArgs; + const [ resource, options ] = args; + const bodyBefore = options?.body; + if ( Boolean(bodyBefore) === false ) { return context.reflect(); } + const bodyAfter = filterBody(bodyBefore); + if ( bodyAfter === undefined || bodyAfter === bodyBefore ) { + return context.reflect(); + } + if ( propNeedles.size !== 0 ) { + const props = collateFetchArgumentsFn(resource, options); + const matched = matchObjectPropertiesFn(propNeedles, props); + if ( matched === undefined ) { return context.reflect(); } + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); + } + } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `After edit:\n${bodyAfter}`); + } + options.body = bodyAfter; + return context.reflect(); + }; + proxyApplyFn('fetch', proxyHandler); + proxyApplyFn('Request', proxyHandler); +} +function trustedJsonEditFetchRequest(jsonq = '', ...args) { + jsonEditFetchRequestFn(true, jsonq, ...args); +}; +trustedJsonEditFetchRequest(...args); +}, +}; + + +scriptlets['jsonl-edit-xhr-response.js'] = { +aliases: [], + +requiresTrust: false, +func: function (scriptletGlobals = {}, ...args) { +function parsePropertiesToMatchFn(propsToMatch, implicit = '') { + const safe = safeSelf(); + const needles = new Map(); + if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } + const options = { canNegate: true }; + for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { + let [ prop, pattern ] = safe.String_split.call(needle, ':'); + if ( prop === '' ) { continue; } + if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { + prop = `${prop}:${pattern}`; + pattern = undefined; + } + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); + } + } + return needles; +} +function matchObjectPropertiesFn(propNeedles, ...objs) { + const safe = safeSelf(); + const matched = []; + for ( const obj of objs ) { + if ( obj instanceof Object === false ) { continue; } + for ( const [ prop, details ] of propNeedles ) { + let value = obj[prop]; + if ( value === undefined ) { continue; } + if ( typeof value !== 'string' ) { + try { value = safe.JSON_stringify(value); } + catch { } + if ( typeof value !== 'string' ) { continue; } + } + if ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); + } + } + return matched; +} +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + 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 /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + onIdle(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); + }, + offIdle(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); + } + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +function jsonlEditFn(jsonp, text = '') { + const safe = safeSelf(); + const lineSeparator = /\r?\n/.exec(text)?.[0] || '\n'; + const linesBefore = text.split('\n'); + const linesAfter = []; + for ( const lineBefore of linesBefore ) { + let obj; + try { obj = safe.JSON_parse(lineBefore); } catch { } + if ( typeof obj !== 'object' || obj === null ) { + linesAfter.push(lineBefore); + continue; + } + const objAfter = jsonp.apply(obj); + if ( objAfter === undefined ) { + linesAfter.push(lineBefore); + continue; + } + const lineAfter = safe.JSON_stringify(objAfter); + linesAfter.push(lineAfter); + } + return linesAfter.join(lineSeparator); +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { + r.modify = 'repl'; + val = query.slice(r.i+6, -1); + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reIndice = /^-?\d+/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + i = this.#compileExpr(query, step, i); + break; + } + const s = this.#consumeUnquotedIdentifier(query, i); + if ( s === undefined ) { return; } + steps.push({ mv, k: s }); + i += s.length; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */; + const j = i + 2 + (not ? 1 : 0); + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: + resultset = this.#getMatches(resultset, step); + break; + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( step.k === '*' ) { + this.#getMatchesFromAll(pathin, step, owner, listout); + } else if ( step.k !== undefined ) { + this.#getMatchesFromKeys(pathin, step, owner, listout); + } else if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + } + } + return listout; + } + #getMatchesFromAll(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + for ( const { path } of this.#getDescendants(owner, recursive) ) { + out.push([ ...pathin, ...path ]); + } + } + #getMatchesFromKeys(pathin, step, owner, out) { + const kk = Array.isArray(step.k) ? step.k : [ step.k ]; + for ( const k of kk ) { + const normalized = this.#evaluateExpr(step, owner, k); + if ( normalized === undefined ) { continue; } + out.push([ ...pathin, normalized ]); + } + if ( step.mv !== this.#DESCENDANTS ) { return; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + for ( const k of kk ) { + const normalized = this.#evaluateExpr(step, obj[key], k); + if ( normalized === undefined ) { continue; } + out.push([ ...pathin, ...path, normalized ]); + } + } + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + if ( Array.isArray(owner) === false ) { + const r = this.#evaluate(step.steps, pathin); + if ( r.length !== 0 ) { out.push(pathin); } + if ( recursive !== true ) { return; } + } + for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { + if ( Array.isArray(obj[key]) ) { continue; } + const q = [ ...pathin, ...path ]; + const r = this.#evaluate(step.steps, q); + if ( r.length === 0 ) { continue; } + out.push(q); + } + } + #normalizeKey(owner, key) { + if ( typeof key === 'number' ) { + if ( Array.isArray(owner) ) { + return key >= 0 ? key : owner.length + key; + } + } + return key; + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: Object.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ ) { + i += 1; + continue; + } + if ( c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const s = this.#consumeUnquotedIdentifier(query, i); + if ( s === undefined ) { return; } + keys.push(s); + i += s.length; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return match[0]; + } + #untilChar(query, targetCharCode, i) { + const len = query.length; + const parts = []; + let beg = i, end = i; + for (;;) { + if ( end === len ) { return; } + const c = query.charCodeAt(end); + if ( c === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #compileExpr(query, step, i) { + if ( query.startsWith('=/', i) ) { + const r = this.#untilChar(query, 0x2F /* / */, i+2); + if ( r === undefined ) { return i; } + const match = /^[i]/.exec(query.slice(r.i)); + try { + step.rval = new RegExp(r.s, match && match[0] || undefined); + } catch { + return i; + } + step.op = 're'; + if ( match ) { r.i += match[0].length; } + return r.i; + } + const match = this.#reExpr.exec(query.slice(i)); + if ( match === null ) { return i; } + try { + step.rval = JSON.parse(match[2]); + step.op = match[1]; + } catch { + } + return i + match[1].length + match[2].length; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, key) { + if ( owner === undefined || owner === null ) { return; } + if ( typeof key === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + } + const k = this.#normalizeKey(owner, key); + const hasOwn = Object.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + let outcome = false; + switch ( step.op ) { + case '==': outcome = (v === step.rval) === target; break; + case '!=': outcome = (v !== step.rval) === target; break; + case '<': outcome = (v < step.rval) === target; break; + case '<=': outcome = (v <= step.rval) === target; break; + case '>': outcome = (v > step.rval) === target; break; + case '>=': outcome = (v >= step.rval) === target; break; + case '^=': outcome = `${v}`.startsWith(step.rval) === target; break; + case '$=': outcome = `${v}`.endsWith(step.rval) === target; break; + case '*=': outcome = `${v}`.includes(step.rval) === target; break; + case 're': outcome = step.rval.test(`${v}`); break; + default: outcome = hasOwn === target; break; + } + if ( outcome ) { return k; } + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of Object.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new RegExp(rval.regex, rval.flags) + : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { + } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +function jsonlEditXhrResponseFn(trusted, jsonq = '') { + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}jsonl-edit-xhr-response`, + jsonq + ); + const xhrInstances = new WeakMap(); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); + const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); + self.XMLHttpRequest = class extends self.XMLHttpRequest { + open(method, url, ...args) { + const xhrDetails = { method, url }; + const matched = propNeedles.size === 0 || + matchObjectPropertiesFn(propNeedles, xhrDetails); + if ( matched ) { + if ( safe.logLevel > 1 && Array.isArray(matched) ) { + safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`); + } + xhrInstances.set(this, xhrDetails); + } + return super.open(method, url, ...args); + } + get response() { + const innerResponse = super.response; + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails === undefined ) { + return innerResponse; + } + const responseLength = typeof innerResponse === 'string' + ? innerResponse.length + : undefined; + if ( xhrDetails.lastResponseLength !== responseLength ) { + xhrDetails.response = undefined; + xhrDetails.lastResponseLength = responseLength; + } + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; + } + if ( typeof innerResponse !== 'string' ) { + return (xhrDetails.response = innerResponse); + } + const outerResponse = jsonlEditFn(jsonp, innerResponse); + if ( outerResponse !== innerResponse ) { + safe.uboLog(logPrefix, 'Pruned'); + } + return (xhrDetails.response = outerResponse); + } + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; + } + }; +} +function jsonlEditXhrResponse(jsonq = '', ...args) { + jsonlEditXhrResponseFn(false, jsonq, ...args); +}; +jsonlEditXhrResponse(...args); +}, +}; + + +scriptlets['trusted-jsonl-edit-xhr-response.js'] = { +aliases: [], + +requiresTrust: true, +func: function (scriptletGlobals = {}, ...args) { +function parsePropertiesToMatchFn(propsToMatch, implicit = '') { + const safe = safeSelf(); + const needles = new Map(); + if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } + const options = { canNegate: true }; + for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { + let [ prop, pattern ] = safe.String_split.call(needle, ':'); + if ( prop === '' ) { continue; } + if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { + prop = `${prop}:${pattern}`; + pattern = undefined; + } + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); + } + } + return needles; +} +function matchObjectPropertiesFn(propNeedles, ...objs) { + const safe = safeSelf(); + const matched = []; + for ( const obj of objs ) { + if ( obj instanceof Object === false ) { continue; } + for ( const [ prop, details ] of propNeedles ) { + let value = obj[prop]; + if ( value === undefined ) { continue; } + if ( typeof value !== 'string' ) { + try { value = safe.JSON_stringify(value); } + catch { } + if ( typeof value !== 'string' ) { continue; } + } + if ( safe.testPattern(details, value) === false ) { return; } + matched.push(`${prop}: ${value}`); + } + } + return matched; +} +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + 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 /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + onIdle(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); + }, + offIdle(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); + } + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +function jsonlEditFn(jsonp, text = '') { + const safe = safeSelf(); + const lineSeparator = /\r?\n/.exec(text)?.[0] || '\n'; + const linesBefore = text.split('\n'); + const linesAfter = []; + for ( const lineBefore of linesBefore ) { + let obj; + try { obj = safe.JSON_parse(lineBefore); } catch { } + if ( typeof obj !== 'object' || obj === null ) { + linesAfter.push(lineBefore); + continue; + } + const objAfter = jsonp.apply(obj); + if ( objAfter === undefined ) { + linesAfter.push(lineBefore); + continue; + } + const lineAfter = safe.JSON_stringify(objAfter); + linesAfter.push(lineAfter); + } + return linesAfter.join(lineSeparator); +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { + r.modify = 'repl'; + val = query.slice(r.i+6, -1); + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -14615,7 +16185,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -15515,7 +17085,7 @@ class JSONPath { } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -25601,7 +27171,7 @@ function trustedReplaceArgument( replacer = ( ) => value; } const reCondition = extraArgs.condition - ? safe.patternToRegex(extraArgs.condition) + ? safe.patternToRegex(`${extraArgs.condition}`) : /^/; const getArg = context => { if ( argposRaw === 'this' ) { return context.thisArg; } @@ -34613,12 +36183,23 @@ function trustedClickElement( return elem.shadowRoot; }; + const queryOrEvaluateSelector = (selector, context) => { + if ( selector.startsWith('xpath:') === false ) { + return context.querySelector(selector); + } + const result = document.evaluate(selector.slice(6), context, null, 9, null); + if ( result.resultType !== 9 ) { return null; } + const elem = result.singleNodeValue; + if ( elem?.nodeType !== 1 ) { return null; } + return elem; + } + const querySelectorEx = (selector, context = document) => { const pos = selector.indexOf(' >>> '); - if ( pos === -1 ) { return context.querySelector(selector); } + if ( pos === -1 ) { return queryOrEvaluateSelector(selector, context); } const outside = selector.slice(0, pos).trim(); const inside = selector.slice(pos + 5).trim(); - const elem = context.querySelector(outside); + const elem = queryOrEvaluateSelector(outside, context); if ( elem === null ) { return null; } const shadowRoot = getShadowRoot(elem); return shadowRoot && querySelectorEx(inside, shadowRoot); @@ -34639,7 +36220,7 @@ function trustedClickElement( steps.unshift(clickDelay); } if ( typeof steps.at(-1) !== 'number' ) { - steps.push(10000); + steps.push(11000); } const waitForTime = ms => { @@ -34659,34 +36240,39 @@ function trustedClickElement( }; const waitForElement = selector => { + safe.uboLog(logPrefix, `Waiting for ${selector}`); return new Promise(resolve => { - const elem = querySelectorEx(selector); - if ( elem !== null ) { - elem.click(); - resolve(); - return; - } - safe.uboLog(logPrefix, `Waiting for ${selector}`); - const observer = new MutationObserver(( ) => { - const elem = querySelectorEx(selector); - if ( elem === null ) { return; } - waitForElement.cancel(); - elem.click(); - resolve(); - }); - observer.observe(document, { - attributes: true, - childList: true, - subtree: true, - }); - waitForElement.observer = observer; + waitForElement.check(selector, resolve); + }); + }; + waitForElement.lookup = directive => { + const beVisible = directive.startsWith('when-visible:'); + const selector = beVisible ? directive.slice(13) : directive; + const elem = querySelectorEx(selector); + if ( Boolean(elem) === false ) { return null; } + if ( beVisible !== true ) { return elem; } + const isVisible = elem.checkVisibility({ + opacityProperty: true, + visibilityProperty: true, }); + return isVisible ? elem : null; + }; + waitForElement.check = (directive, resolve) => { + const elem = waitForElement.lookup(directive); + if ( elem ) { + waitForElement.cbid = undefined; + elem.click(); + return resolve(); + } + waitForElement.cbid = safe.onIdle(( ) => { + waitForElement.check(directive, resolve); + }, { timeout: 67 }); }; waitForElement.cancel = ( ) => { - const { observer } = waitForElement; - if ( observer === undefined ) { return; } - waitForElement.observer = undefined; - observer.disconnect(); + const { cbid } = waitForElement; + if ( cbid === undefined ) { return; } + waitForElement.cbid = undefined; + safe.offIdle(cbid); }; const waitForTimeout = ms => {