summaryrefslogtreecommitdiffstats
path: root/assets/resources/scriptlets.js
diff options
context:
space:
mode:
Diffstat (limited to 'assets/resources/scriptlets.js')
-rw-r--r--assets/resources/scriptlets.js1126
1 files changed, 727 insertions, 399 deletions
diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js
index 6e2654f..bbef272 100644
--- a/assets/resources/scriptlets.js
+++ b/assets/resources/scriptlets.js
@@ -22,11 +22,11 @@
web page context.
*/
+/* eslint no-prototype-builtins: 0 */
+
// Externally added to the private namespace in which scriptlets execute.
/* global scriptletGlobals */
-'use strict';
-
export const builtinScriptlets = [];
/*******************************************************************************
@@ -42,8 +42,8 @@ builtinScriptlets.push({
fn: safeSelf,
});
function safeSelf() {
- if ( scriptletGlobals.has('safeSelf') ) {
- return scriptletGlobals.get('safeSelf');
+ if ( scriptletGlobals.safeSelf ) {
+ return scriptletGlobals.safeSelf;
}
const self = globalThis;
const safe = {
@@ -55,7 +55,10 @@ function safeSelf() {
'Math_max': Math.max,
'Math_min': Math.min,
'Math_random': Math.random,
+ 'Object': Object,
'Object_defineProperty': Object.defineProperty.bind(Object),
+ 'Object_fromEntries': Object.fromEntries.bind(Object),
+ 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object),
'RegExp': self.RegExp,
'RegExp_test': self.RegExp.prototype.test,
'RegExp_exec': self.RegExp.prototype.exec,
@@ -70,11 +73,25 @@ function safeSelf() {
'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 ( scriptletGlobals.has('canDebug') === false ) { return; }
- if ( args.length === 0 ) { return; }
- if ( `${args[0]}` === '' ) { return; }
- this.log('[uBO]', ...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 === '' ) {
@@ -96,8 +113,7 @@ function safeSelf() {
}
if ( options.flags !== undefined ) {
return {
- re: new this.RegExp(pattern.replace(
- /[.*+?^${}()|[\]\\]/g, '\\$&'),
+ re: new this.RegExp(this.escapeRegexChars(pattern),
options.flags
),
expect,
@@ -116,7 +132,7 @@ function safeSelf() {
if ( pattern === '' ) { return /^/; }
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
if ( match === null ) {
- const reStr = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const reStr = this.escapeRegexChars(pattern);
return new RegExp(verbatim ? `^${reStr}$` : reStr, flags);
}
try {
@@ -137,10 +153,42 @@ function safeSelf() {
}
return out;
}, []);
- return Object.fromEntries(entries);
+ return this.Object_fromEntries(entries);
},
};
- scriptletGlobals.set('safeSelf', safe);
+ scriptletGlobals.safeSelf = safe;
+ if ( scriptletGlobals.bcSecret === undefined ) { return safe; }
+ // This is executed only when the logger is opened
+ const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret);
+ let bcBuffer = [];
+ safe.logLevel = scriptletGlobals.logLevel || 1;
+ safe.sendToLogger = (type, ...args) => {
+ if ( args.length === 0 ) { return; }
+ const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`;
+ if ( bcBuffer === undefined ) {
+ return bc.postMessage({ what: 'messageToLogger', type, text });
+ }
+ bcBuffer.push({ type, text });
+ };
+ bc.onmessage = ev => {
+ const msg = ev.data;
+ switch ( msg ) {
+ case 'iamready!':
+ if ( bcBuffer === undefined ) { break; }
+ bcBuffer.forEach(({ type, text }) =>
+ bc.postMessage({ what: 'messageToLogger', type, text })
+ );
+ bcBuffer = undefined;
+ break;
+ case 'setScriptletLogLevelToOne':
+ safe.logLevel = 1;
+ break;
+ case 'setScriptletLogLevelToTwo':
+ safe.logLevel = 2;
+ break;
+ }
+ };
+ bc.postMessage('areyouready?');
return safe;
}
@@ -176,18 +224,7 @@ builtinScriptlets.push({
});
function shouldDebug(details) {
if ( details instanceof Object === false ) { return false; }
- return scriptletGlobals.has('canDebug') && details.debug;
-}
-
-/******************************************************************************/
-
-builtinScriptlets.push({
- name: 'should-log.fn',
- fn: shouldLog,
-});
-function shouldLog(details) {
- if ( details instanceof Object === false ) { return false; }
- return scriptletGlobals.has('canDebug') && details.log;
+ return scriptletGlobals.canDebug && details.debug;
}
/******************************************************************************/
@@ -292,12 +329,12 @@ function generateContentFn(directive) {
return Promise.resolve(randomize(len | 0));
}
}
- if ( directive.startsWith('war:') && scriptletGlobals.has('warOrigin') ) {
+ if ( directive.startsWith('war:') && scriptletGlobals.warOrigin ) {
return new Promise(resolve => {
- const warOrigin = scriptletGlobals.get('warOrigin');
+ const warOrigin = scriptletGlobals.warOrigin;
const warName = directive.slice(4);
const fullpath = [ warOrigin, '/', warName ];
- const warSecret = scriptletGlobals.get('warSecret');
+ const warSecret = scriptletGlobals.warSecret;
if ( warSecret !== undefined ) {
fullpath.push('?secret=', warSecret);
}
@@ -322,7 +359,6 @@ builtinScriptlets.push({
'get-exception-token.fn',
'safe-self.fn',
'should-debug.fn',
- 'should-log.fn',
],
});
// Issues to mind before changing anything:
@@ -335,6 +371,7 @@ function abortCurrentScriptCore(
if ( typeof target !== 'string' ) { return; }
if ( target === '' ) { return; }
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('abort-current-script', target, needle, context);
const reNeedle = safe.patternToRegex(needle);
const reContext = safe.patternToRegex(context);
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
@@ -358,7 +395,6 @@ function abortCurrentScriptCore(
value = owner[prop];
desc = undefined;
}
- const log = shouldLog(extraArgs);
const debug = shouldDebug(extraArgs);
const exceptionToken = getExceptionToken();
const scriptTexts = new WeakMap();
@@ -388,20 +424,29 @@ function abortCurrentScriptCore(
if ( e instanceof HTMLScriptElement === false ) { return; }
if ( e === thisScript ) { return; }
if ( context !== '' && reContext.test(e.src) === false ) {
- if ( debug === 'nomatch' || debug === 'all' ) { debugger; } // jshint ignore: line
+ // eslint-disable-next-line no-debugger
+ if ( debug === 'nomatch' || debug === 'all' ) { debugger; }
return;
}
- if ( log && e.src !== '' ) { safe.uboLog(`matched src: ${e.src}`); }
+ if ( safe.logLevel > 1 && context !== '' ) {
+ safe.uboLog(logPrefix, `Matched src\n${e.src}`);
+ }
const scriptText = getScriptText(e);
if ( reNeedle.test(scriptText) === false ) {
- if ( debug === 'nomatch' || debug === 'all' ) { debugger; } // jshint ignore: line
+ // eslint-disable-next-line no-debugger
+ if ( debug === 'nomatch' || debug === 'all' ) { debugger; }
return;
}
- if ( log ) { safe.uboLog(`matched script text: ${scriptText}`); }
- if ( debug === 'match' || debug === 'all' ) { debugger; } // jshint ignore: line
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Matched text\n${scriptText}`);
+ }
+ // eslint-disable-next-line no-debugger
+ if ( debug === 'match' || debug === 'all' ) { debugger; }
+ safe.uboLog(logPrefix, 'Aborted');
throw new ReferenceError(exceptionToken);
};
- if ( debug === 'install' ) { debugger; } // jshint ignore: line
+ // eslint-disable-next-line no-debugger
+ if ( debug === 'install' ) { debugger; }
try {
Object.defineProperty(owner, prop, {
get: function() {
@@ -420,40 +465,97 @@ function abortCurrentScriptCore(
}
});
} catch(ex) {
- if ( log ) { safe.uboLog(ex); }
+ safe.uboErr(logPrefix, `Error: ${ex}`);
}
}
/******************************************************************************/
builtinScriptlets.push({
- name: 'set-constant-core.fn',
- fn: setConstantCore,
+ name: 'validate-constant.fn',
+ fn: validateConstantFn,
dependencies: [
- 'run-at.fn',
'safe-self.fn',
],
});
+function validateConstantFn(trusted, raw) {
+ const safe = safeSelf();
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ let value;
+ if ( raw === 'undefined' ) {
+ value = undefined;
+ } else if ( raw === 'false' ) {
+ value = false;
+ } else if ( raw === 'true' ) {
+ value = true;
+ } else if ( raw === 'null' ) {
+ value = null;
+ } else if ( raw === "''" || raw === '' ) {
+ value = '';
+ } else if ( raw === '[]' || raw === 'emptyArr' ) {
+ value = [];
+ } else if ( raw === '{}' || raw === 'emptyObj' ) {
+ value = {};
+ } else if ( raw === 'noopFunc' ) {
+ value = function(){};
+ } else if ( raw === 'trueFunc' ) {
+ value = function(){ return true; };
+ } else if ( raw === 'falseFunc' ) {
+ value = function(){ return false; };
+ } else if ( /^-?\d+$/.test(raw) ) {
+ value = parseInt(raw);
+ if ( isNaN(raw) ) { return; }
+ if ( Math.abs(raw) > 0x7FFF ) { return; }
+ } else if ( trusted ) {
+ if ( raw.startsWith('{') && raw.endsWith('}') ) {
+ try { value = safe.JSON_parse(raw).value; } catch(ex) { return; }
+ }
+ } else {
+ return;
+ }
+ if ( extraArgs.as !== undefined ) {
+ if ( extraArgs.as === 'function' ) {
+ return ( ) => value;
+ } else if ( extraArgs.as === 'callback' ) {
+ return ( ) => (( ) => value);
+ } else if ( extraArgs.as === 'resolved' ) {
+ return Promise.resolve(value);
+ } else if ( extraArgs.as === 'rejected' ) {
+ return Promise.reject(value);
+ }
+ }
+ return value;
+}
+
+/******************************************************************************/
-function setConstantCore(
+builtinScriptlets.push({
+ name: 'set-constant.fn',
+ fn: setConstantFn,
+ dependencies: [
+ 'run-at.fn',
+ 'safe-self.fn',
+ 'validate-constant.fn',
+ ],
+});
+function setConstantFn(
trusted = false,
chain = '',
- cValue = ''
+ rawValue = ''
) {
if ( chain === '' ) { return; }
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('set-constant', chain, rawValue);
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
- function setConstant(chain, cValue) {
+ function setConstant(chain, rawValue) {
const trappedProp = (( ) => {
const pos = chain.lastIndexOf('.');
if ( pos === -1 ) { return chain; }
return chain.slice(pos+1);
})();
- if ( trappedProp === '' ) { return; }
- const thisScript = document.currentScript;
const cloakFunc = fn => {
safe.Object_defineProperty(fn, 'name', { value: trappedProp });
- const proxy = new Proxy(fn, {
+ return new Proxy(fn, {
defineProperty(target, prop) {
if ( prop !== 'toString' ) {
return Reflect.defineProperty(...arguments);
@@ -475,50 +577,12 @@ function setConstantCore(
return Reflect.get(...arguments);
},
});
- return proxy;
};
- if ( cValue === 'undefined' ) {
- cValue = undefined;
- } else if ( cValue === 'false' ) {
- cValue = false;
- } else if ( cValue === 'true' ) {
- cValue = true;
- } else if ( cValue === 'null' ) {
- cValue = null;
- } else if ( cValue === "''" || cValue === '' ) {
- cValue = '';
- } else if ( cValue === '[]' || cValue === 'emptyArr' ) {
- cValue = [];
- } else if ( cValue === '{}' || cValue === 'emptyObj' ) {
- cValue = {};
- } else if ( cValue === 'noopFunc' ) {
- cValue = cloakFunc(function(){});
- } else if ( cValue === 'trueFunc' ) {
- cValue = cloakFunc(function(){ return true; });
- } else if ( cValue === 'falseFunc' ) {
- cValue = cloakFunc(function(){ return false; });
- } else if ( /^-?\d+$/.test(cValue) ) {
- cValue = parseInt(cValue);
- if ( isNaN(cValue) ) { return; }
- if ( Math.abs(cValue) > 0x7FFF ) { return; }
- } else if ( trusted ) {
- if ( cValue.startsWith('{') && cValue.endsWith('}') ) {
- try { cValue = safe.JSON_parse(cValue).value; } catch(ex) { return; }
- }
- } else {
- return;
- }
- if ( extraArgs.as !== undefined ) {
- const value = cValue;
- if ( extraArgs.as === 'function' ) {
- cValue = ( ) => value;
- } else if ( extraArgs.as === 'callback' ) {
- cValue = ( ) => (( ) => value);
- } else if ( extraArgs.as === 'resolved' ) {
- cValue = Promise.resolve(value);
- } else if ( extraArgs.as === 'rejected' ) {
- cValue = Promise.reject(value);
- }
+ if ( trappedProp === '' ) { return; }
+ const thisScript = document.currentScript;
+ let normalValue = validateConstantFn(trusted, rawValue);
+ if ( rawValue === 'noopFunc' || rawValue === 'trueFunc' || rawValue === 'falseFunc' ) {
+ normalValue = cloakFunc(normalValue);
}
let aborted = false;
const mustAbort = function(v) {
@@ -526,18 +590,21 @@ function setConstantCore(
if ( aborted ) { return true; }
aborted =
(v !== undefined && v !== null) &&
- (cValue !== undefined && cValue !== null) &&
- (typeof v !== typeof cValue);
+ (normalValue !== undefined && normalValue !== null) &&
+ (typeof v !== typeof normalValue);
+ if ( aborted ) {
+ safe.uboLog(logPrefix, `Aborted because value set to ${v}`);
+ }
return aborted;
};
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
// Support multiple trappers for the same property.
const trapProp = function(owner, prop, configurable, handler) {
- if ( handler.init(configurable ? owner[prop] : cValue) === false ) { return; }
- const odesc = Object.getOwnPropertyDescriptor(owner, prop);
+ if ( handler.init(configurable ? owner[prop] : normalValue) === false ) { return; }
+ const odesc = safe.Object_getOwnPropertyDescriptor(owner, prop);
let prevGetter, prevSetter;
- if ( odesc instanceof Object ) {
- owner[prop] = cValue;
+ if ( odesc instanceof safe.Object ) {
+ owner[prop] = normalValue;
if ( odesc.get instanceof Function ) {
prevGetter = odesc.get;
}
@@ -552,7 +619,7 @@ function setConstantCore(
if ( prevGetter !== undefined ) {
prevGetter();
}
- return handler.getter(); // cValue
+ return handler.getter();
},
set(a) {
if ( prevSetter !== undefined ) {
@@ -561,7 +628,9 @@ function setConstantCore(
handler.setter(a);
}
});
+ safe.uboLog(logPrefix, 'Trap installed');
} catch(ex) {
+ safe.uboErr(logPrefix, ex);
}
};
const trapChain = function(owner, chain) {
@@ -575,13 +644,15 @@ function setConstantCore(
return true;
},
getter: function() {
- return document.currentScript === thisScript
- ? this.v
- : cValue;
+ if ( document.currentScript === thisScript ) {
+ return this.v;
+ }
+ safe.uboLog(logPrefix, 'Property read');
+ return normalValue;
},
setter: function(a) {
if ( mustAbort(a) === false ) { return; }
- cValue = a;
+ normalValue = a;
}
});
return;
@@ -589,7 +660,7 @@ function setConstantCore(
const prop = chain.slice(0, pos);
const v = owner[prop];
chain = chain.slice(pos + 1);
- if ( v instanceof Object || typeof v === 'object' && v !== null ) {
+ if ( v instanceof safe.Object || typeof v === 'object' && v !== null ) {
trapChain(v, chain);
return;
}
@@ -604,7 +675,7 @@ function setConstantCore(
},
setter: function(a) {
this.v = a;
- if ( a instanceof Object ) {
+ if ( a instanceof safe.Object ) {
trapChain(a, chain);
}
}
@@ -613,7 +684,7 @@ function setConstantCore(
trapChain(window, chain);
}
runAt(( ) => {
- setConstant(chain, cValue);
+ setConstant(chain, rawValue);
}, extraArgs.runAt);
}
@@ -633,18 +704,18 @@ function replaceNodeTextFn(
replacement = ''
) {
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('replace-node-text.fn', ...Array.from(arguments));
const reNodeName = safe.patternToRegex(nodeName, 'i', true);
const rePattern = safe.patternToRegex(pattern, 'gms');
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
- const shouldLog = scriptletGlobals.has('canDebug') && extraArgs.log || 0;
const reCondition = safe.patternToRegex(extraArgs.condition || '', 'ms');
const stop = (takeRecord = true) => {
if ( takeRecord ) {
handleMutations(observer.takeRecords());
}
observer.disconnect();
- if ( shouldLog !== 0 ) {
- safe.uboLog(`replace-node-text-core.fn: quitting "${pattern}" => "${replacement}"`);
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, 'Quitting');
}
};
let sedCount = extraArgs.sedCount || 0;
@@ -659,10 +730,10 @@ function replaceNodeTextFn(
? before.replace(rePattern, replacement)
: replacement;
node.textContent = after;
- if ( shouldLog !== 0 ) {
- safe.uboLog('replace-node-text.fn before:\n', before);
- safe.uboLog('replace-node-text.fn after:\n', after);
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Text before:\n${before.trim()}`);
}
+ safe.uboLog(logPrefix, `Text after:\n${after.trim()}`);
return sedCount === 0 || (sedCount -= 1) !== 0;
};
const handleMutations = mutations => {
@@ -690,9 +761,7 @@ function replaceNodeTextFn(
if ( handleNode(node) ) { continue; }
stop(); break;
}
- if ( shouldLog !== 0 ) {
- safe.uboLog(`replace-node-text-core.fn ${count} nodes present before installing mutation observer`);
- }
+ safe.uboLog(logPrefix, `${count} nodes present before installing mutation observer`);
}
if ( extraArgs.stay ) { return; }
runAt(( ) => {
@@ -713,8 +782,6 @@ builtinScriptlets.push({
dependencies: [
'matches-stack-trace.fn',
'object-find-owner.fn',
- 'safe-self.fn',
- 'should-log.fn',
],
});
// When no "prune paths" argument is provided, the scriptlet is
@@ -731,15 +798,12 @@ function objectPruneFn(
extraArgs = {}
) {
if ( typeof rawPrunePaths !== 'string' ) { return; }
- const safe = safeSelf();
const prunePaths = rawPrunePaths !== ''
? rawPrunePaths.split(/ +/)
: [];
const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== ''
? rawNeedlePaths.split(/ +/)
: [];
- const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log });
- const reLogNeedle = safe.patternToRegex(logLevel === true ? rawNeedlePaths : '');
if ( stackNeedleDetails.matchAll !== true ) {
if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) {
return;
@@ -754,14 +818,6 @@ function objectPruneFn(
}
return true;
};
- objectPruneFn.logJson = (json, msg, reNeedle) => {
- if ( reNeedle.test(json) === false ) { return; }
- safeSelf().uboLog(`objectPrune()`, msg, location.hostname, json);
- };
- }
- const jsonBefore = logLevel ? safe.JSON_stringify(obj, null, 2) : '';
- if ( logLevel === true || logLevel === 'all' ) {
- objectPruneFn.logJson(jsonBefore, `prune:"${rawPrunePaths}" log:"${logLevel}"`, reLogNeedle);
}
if ( prunePaths.length === 0 ) { return; }
let outcome = 'nomatch';
@@ -772,9 +828,6 @@ function objectPruneFn(
}
}
}
- if ( logLevel === outcome ) {
- objectPruneFn.logJson(jsonBefore, `prune:"${rawPrunePaths}" log:"${logLevel}"`, reLogNeedle);
- }
if ( outcome === 'match' ) { return obj; }
}
@@ -812,14 +865,33 @@ function objectFindOwnerFn(
return modified;
}
const prop = chain.slice(0, pos);
+ const next = chain.slice(pos + 1);
+ let found = false;
+ if ( prop === '[-]' && Array.isArray(owner) ) {
+ let i = owner.length;
+ while ( i-- ) {
+ if ( objectFindOwnerFn(owner[i], next) === false ) { continue; }
+ owner.splice(i, 1);
+ found = true;
+ }
+ return found;
+ }
+ if ( prop === '{-}' && owner instanceof Object ) {
+ for ( const key of Object.keys(owner) ) {
+ if ( objectFindOwnerFn(owner[key], next) === false ) { continue; }
+ delete owner[key];
+ found = true;
+ }
+ return found;
+ }
if (
prop === '[]' && Array.isArray(owner) ||
+ prop === '{}' && owner instanceof Object ||
prop === '*' && owner instanceof Object
) {
- const next = chain.slice(pos + 1);
- let found = false;
for ( const key of Object.keys(owner) ) {
- found = objectFindOwnerFn(owner[key], next, prune) || found;
+ if (objectFindOwnerFn(owner[key], next, prune) === false ) { continue; }
+ found = true;
}
return found;
}
@@ -827,7 +899,57 @@ function objectFindOwnerFn(
owner = owner[prop];
chain = chain.slice(pos + 1);
}
- return true;
+}
+
+/******************************************************************************/
+
+builtinScriptlets.push({
+ name: 'get-all-cookies.fn',
+ fn: getAllCookiesFn,
+});
+function getAllCookiesFn() {
+ return document.cookie.split(/\s*;\s*/).map(s => {
+ const pos = s.indexOf('=');
+ if ( pos === 0 ) { return; }
+ if ( pos === -1 ) { return `${s.trim()}=`; }
+ const key = s.slice(0, pos).trim();
+ const value = s.slice(pos+1).trim();
+ return { key, value };
+ }).filter(s => s !== undefined);
+}
+
+/******************************************************************************/
+
+builtinScriptlets.push({
+ name: 'get-all-local-storage.fn',
+ fn: getAllLocalStorageFn,
+});
+function getAllLocalStorageFn(which = 'localStorage') {
+ const storage = self[which];
+ const out = [];
+ for ( let i = 0; i < storage.length; i++ ) {
+ const key = storage.key(i);
+ const value = storage.getItem(key);
+ return { key, value };
+ }
+ return out;
+}
+
+/******************************************************************************/
+
+builtinScriptlets.push({
+ name: 'get-cookie.fn',
+ fn: getCookieFn,
+});
+function getCookieFn(
+ name = ''
+) {
+ for ( const s of document.cookie.split(/\s*;\s*/) ) {
+ const pos = s.indexOf('=');
+ if ( pos === -1 ) { continue; }
+ if ( s.slice(0, pos) !== name ) { continue; }
+ return s.slice(pos+1).trim();
+ }
}
/******************************************************************************/
@@ -835,6 +957,9 @@ function objectFindOwnerFn(
builtinScriptlets.push({
name: 'set-cookie.fn',
fn: setCookieFn,
+ dependencies: [
+ 'get-cookie.fn',
+ ],
});
function setCookieFn(
trusted = false,
@@ -844,16 +969,17 @@ function setCookieFn(
path = '',
options = {},
) {
- const getCookieValue = name => {
- for ( const s of document.cookie.split(/\s*;\s*/) ) {
- const pos = s.indexOf('=');
- if ( pos === -1 ) { continue; }
- if ( s.slice(0, pos) !== name ) { continue; }
- return s.slice(pos+1);
- }
- };
+ // https://datatracker.ietf.org/doc/html/rfc2616#section-2.2
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/2777
+ if ( trusted === false && /[^!#$%&'*+\-.0-9A-Z[\]^_`a-z|~]/.test(name) ) {
+ name = encodeURIComponent(name);
+ }
+ // https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
+ if ( /[^!#-+\--:<-[\]-~]/.test(value) ) {
+ value = encodeURIComponent(value);
+ }
- const cookieBefore = getCookieValue(name);
+ const cookieBefore = getCookieFn(name);
if ( cookieBefore !== undefined && options.dontOverwrite ) { return; }
if ( cookieBefore === value && options.reload ) { return; }
@@ -881,9 +1007,12 @@ function setCookieFn(
} catch(_) {
}
- if ( options.reload && getCookieValue(name) === value ) {
+ const done = getCookieFn(name) === value;
+ if ( done && options.reload ) {
window.location.reload();
}
+
+ return done;
}
/******************************************************************************/
@@ -1070,7 +1199,7 @@ function matchObjectProperties(propNeedles, ...objs) {
let value = haystack[prop];
if ( value === undefined ) { continue; }
if ( typeof value !== 'string' ) {
- try { value = JSON.stringify(value); }
+ try { value = safe.JSON_stringify(value); }
catch(ex) { }
if ( typeof value !== 'string' ) { continue; }
}
@@ -1090,7 +1219,6 @@ builtinScriptlets.push({
'object-prune.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
- 'should-log.fn',
],
});
function jsonPruneFetchResponseFn(
@@ -1098,23 +1226,22 @@ function jsonPruneFetchResponseFn(
rawNeedlePaths = ''
) {
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('json-prune-fetch-response', rawPrunePaths, rawNeedlePaths);
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
- const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log, });
- const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { });
const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url');
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
+ const logall = rawPrunePaths === '';
const applyHandler = function(target, thisArg, args) {
const fetchPromise = Reflect.apply(target, thisArg, args);
- if ( logLevel === true ) {
- log('json-prune-fetch-response:', JSON.stringify(Array.from(args)).slice(1,-1));
- }
- if ( rawPrunePaths === '' ) { return fetchPromise; }
- let outcome = 'match';
+ let outcome = logall ? 'nomatch' : 'match';
if ( propNeedles.size !== 0 ) {
const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ];
if ( objs[0] instanceof Request ) {
- try { objs[0] = safe.Request_clone.call(objs[0]); }
- catch(ex) { log(ex); }
+ try {
+ objs[0] = safe.Request_clone.call(objs[0]);
+ } catch(ex) {
+ safe.uboErr(logPrefix, 'Error:', ex);
+ }
}
if ( args[1] instanceof Object ) {
objs.push(args[1]);
@@ -1122,19 +1249,19 @@ function jsonPruneFetchResponseFn(
if ( matchObjectProperties(propNeedles, ...objs) === false ) {
outcome = 'nomatch';
}
- if ( outcome === logLevel || logLevel === 'all' ) {
- log(
- `json-prune-fetch-response (${outcome})`,
- `\n\tfetchPropsToMatch: ${JSON.stringify(Array.from(propNeedles)).slice(1,-1)}`,
- '\n\tprops:', ...objs,
- );
- }
}
- if ( outcome === 'nomatch' ) { return fetchPromise; }
+ if ( logall === false && outcome === 'nomatch' ) { return fetchPromise; }
+ if ( safe.logLevel > 1 && outcome !== 'nomatch' && propNeedles.size !== 0 ) {
+ safe.uboLog(logPrefix, `Matched optional "propsToMatch"\n${extraArgs.propsToMatch}`);
+ }
return fetchPromise.then(responseBefore => {
const response = responseBefore.clone();
return response.json().then(objBefore => {
if ( typeof objBefore !== 'object' ) { return responseBefore; }
+ if ( logall ) {
+ safe.uboLog(logPrefix, safe.JSON_stringify(objBefore, null, 2));
+ return responseBefore;
+ }
const objAfter = objectPruneFn(
objBefore,
rawPrunePaths,
@@ -1143,6 +1270,7 @@ function jsonPruneFetchResponseFn(
extraArgs
);
if ( typeof objAfter !== 'object' ) { return responseBefore; }
+ safe.uboLog(logPrefix, 'Pruned');
const responseAfter = Response.json(objAfter, {
status: responseBefore.status,
statusText: responseBefore.statusText,
@@ -1156,11 +1284,11 @@ function jsonPruneFetchResponseFn(
});
return responseAfter;
}).catch(reason => {
- log('json-prune-fetch-response:', reason);
+ safe.uboErr(logPrefix, 'Error:', reason);
return responseBefore;
});
}).catch(reason => {
- log('json-prune-fetch-response:', reason);
+ safe.uboErr(logPrefix, 'Error:', reason);
return fetchPromise;
});
};
@@ -1178,7 +1306,6 @@ builtinScriptlets.push({
'match-object-properties.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
- 'should-log.fn',
],
});
function replaceFetchResponseFn(
@@ -1189,27 +1316,24 @@ function replaceFetchResponseFn(
) {
if ( trusted !== true ) { return; }
const safe = safeSelf();
- const extraArgs = safe.getExtraArgs(Array.from(arguments), 4);
- const logLevel = shouldLog({
- log: pattern === '' || extraArgs.log,
- });
- const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { });
+ const logPrefix = safe.makeLogPrefix('replace-fetch-response', pattern, replacement, propsToMatch);
if ( pattern === '*' ) { pattern = '.*'; }
const rePattern = safe.patternToRegex(pattern);
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
self.fetch = new Proxy(self.fetch, {
apply: function(target, thisArg, args) {
- if ( logLevel === true ) {
- log('replace-fetch-response:', JSON.stringify(Array.from(args)).slice(1,-1));
- }
const fetchPromise = Reflect.apply(target, thisArg, args);
if ( pattern === '' ) { return fetchPromise; }
let outcome = 'match';
if ( propNeedles.size !== 0 ) {
const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ];
if ( objs[0] instanceof Request ) {
- try { objs[0] = safe.Request_clone.call(objs[0]); }
- catch(ex) { log(ex); }
+ try {
+ objs[0] = safe.Request_clone.call(objs[0]);
+ }
+ catch(ex) {
+ safe.uboErr(logPrefix, ex);
+ }
}
if ( args[1] instanceof Object ) {
objs.push(args[1]);
@@ -1217,28 +1341,18 @@ function replaceFetchResponseFn(
if ( matchObjectProperties(propNeedles, ...objs) === false ) {
outcome = 'nomatch';
}
- if ( outcome === logLevel || logLevel === 'all' ) {
- log(
- `replace-fetch-response (${outcome})`,
- `\n\tpropsToMatch: ${JSON.stringify(Array.from(propNeedles)).slice(1,-1)}`,
- '\n\tprops:', ...args,
- );
- }
}
if ( outcome === 'nomatch' ) { return fetchPromise; }
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Matched "propsToMatch"\n${propsToMatch}`);
+ }
return fetchPromise.then(responseBefore => {
const response = responseBefore.clone();
return response.text().then(textBefore => {
const textAfter = textBefore.replace(rePattern, replacement);
const outcome = textAfter !== textBefore ? 'match' : 'nomatch';
- if ( outcome === logLevel || logLevel === 'all' ) {
- log(
- `replace-fetch-response (${outcome})`,
- `\n\tpattern: ${pattern}`,
- `\n\treplacement: ${replacement}`,
- );
- }
if ( outcome === 'nomatch' ) { return responseBefore; }
+ safe.uboLog(logPrefix, 'Replaced');
const responseAfter = new Response(textAfter, {
status: responseBefore.status,
statusText: responseBefore.statusText,
@@ -1252,17 +1366,48 @@ function replaceFetchResponseFn(
});
return responseAfter;
}).catch(reason => {
- log('replace-fetch-response:', reason);
+ safe.uboErr(logPrefix, reason);
return responseBefore;
});
}).catch(reason => {
- log('replace-fetch-response:', reason);
+ safe.uboErr(logPrefix, reason);
return fetchPromise;
});
}
});
}
+/******************************************************************************/
+
+builtinScriptlets.push({
+ name: 'proxy-apply.fn',
+ fn: proxyApplyFn,
+ dependencies: [
+ 'safe-self.fn',
+ ],
+});
+function proxyApplyFn(
+ target = '',
+ handler = ''
+) {
+ let context = globalThis;
+ let prop = target;
+ for (;;) {
+ const pos = prop.indexOf('.');
+ if ( pos === -1 ) { break; }
+ context = context[prop.slice(0, pos)];
+ if ( context instanceof Object === false ) { return; }
+ prop = prop.slice(pos+1);
+ }
+ const fn = context[prop];
+ if ( typeof fn !== 'function' ) { return; }
+ if ( fn.prototype && fn.prototype.constructor === fn ) {
+ context[prop] = new Proxy(fn, { construct: handler });
+ return (...args) => { return Reflect.construct(...args); };
+ }
+ context[prop] = new Proxy(fn, { apply: handler });
+ return (...args) => { return Reflect.apply(...args); };
+}
/*******************************************************************************
@@ -1303,6 +1448,7 @@ builtinScriptlets.push({
fn: abortOnPropertyRead,
dependencies: [
'get-exception-token.fn',
+ 'safe-self.fn',
],
});
function abortOnPropertyRead(
@@ -1310,8 +1456,11 @@ function abortOnPropertyRead(
) {
if ( typeof chain !== 'string' ) { return; }
if ( chain === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('abort-on-property-read', chain);
const exceptionToken = getExceptionToken();
const abort = function() {
+ safe.uboLog(logPrefix, 'Aborted');
throw new ReferenceError(exceptionToken);
};
const makeProxy = function(owner, chain) {
@@ -1359,6 +1508,7 @@ builtinScriptlets.push({
fn: abortOnPropertyWrite,
dependencies: [
'get-exception-token.fn',
+ 'safe-self.fn',
],
});
function abortOnPropertyWrite(
@@ -1366,6 +1516,8 @@ function abortOnPropertyWrite(
) {
if ( typeof prop !== 'string' ) { return; }
if ( prop === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('abort-on-property-write', prop);
const exceptionToken = getExceptionToken();
let owner = window;
for (;;) {
@@ -1378,6 +1530,7 @@ function abortOnPropertyWrite(
delete owner[prop];
Object.defineProperty(owner, prop, {
set: function() {
+ safe.uboLog(logPrefix, 'Aborted');
throw new ReferenceError(exceptionToken);
}
});
@@ -1462,7 +1615,6 @@ builtinScriptlets.push({
'run-at.fn',
'safe-self.fn',
'should-debug.fn',
- 'should-log.fn',
],
});
// https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120
@@ -1472,32 +1624,68 @@ function addEventListenerDefuser(
) {
const safe = safeSelf();
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ const logPrefix = safe.makeLogPrefix('prevent-addEventListener', type, pattern);
const reType = safe.patternToRegex(type, undefined, true);
const rePattern = safe.patternToRegex(pattern);
- const log = shouldLog(extraArgs);
const debug = shouldDebug(extraArgs);
+ const targetSelector = extraArgs.elements || undefined;
+ const elementMatches = elem => {
+ if ( elem && elem.matches && elem.matches(targetSelector) ) { return true; }
+ const elems = Array.from(document.querySelectorAll(targetSelector));
+ return elems.includes(elem);
+ };
+ const elementDetails = elem => {
+ if ( elem instanceof Window ) { return 'window'; }
+ if ( elem instanceof Document ) { return 'document'; }
+ if ( elem instanceof Element === false ) { return '?'; }
+ const parts = [];
+ if ( elem.id !== '' ) { parts.push(`#${CSS.escape(elem.id)}`); }
+ for ( let i = 0; i < elem.classList.length; i++ ) {
+ parts.push(`.${CSS.escape(elem.classList.item(i))}`);
+ }
+ for ( let i = 0; i < elem.attributes.length; i++ ) {
+ const attr = elem.attributes.item(i);
+ if ( attr.name === 'id' ) { continue; }
+ if ( attr.name === 'class' ) { continue; }
+ parts.push(`[${CSS.escape(attr.name)}="${attr.value}"]`);
+ }
+ return parts.join('');
+ };
+ const shouldPrevent = (thisArg, type, handler) => {
+ const matchesType = safe.RegExp_test.call(reType, type);
+ const matchesHandler = safe.RegExp_test.call(rePattern, handler);
+ const matchesEither = matchesType || matchesHandler;
+ const matchesBoth = matchesType && matchesHandler;
+ if ( debug === 1 && matchesBoth || debug === 2 && matchesEither ) {
+ debugger; // eslint-disable-line no-debugger
+ }
+ if ( matchesBoth && targetSelector !== undefined ) {
+ if ( elementMatches(thisArg) === false ) { return false; }
+ }
+ return matchesBoth;
+ };
const trapEddEventListeners = ( ) => {
const eventListenerHandler = {
apply: function(target, thisArg, args) {
- let type, handler;
+ let t, h;
try {
- type = String(args[0]);
- handler = args[1] instanceof Function
- ? String(safe.Function_toString(args[1]))
- : String(args[1]);
+ t = String(args[0]);
+ if ( typeof args[1] === 'function' ) {
+ h = String(safe.Function_toString(args[1]));
+ } else if ( typeof args[1] === 'object' && args[1] !== null ) {
+ if ( typeof args[1].handleEvent === 'function' ) {
+ h = String(safe.Function_toString(args[1].handleEvent));
+ }
+ } else {
+ h = String(args[1]);
+ }
} catch(ex) {
}
- const matchesType = safe.RegExp_test.call(reType, type);
- const matchesHandler = safe.RegExp_test.call(rePattern, handler);
- const matchesEither = matchesType || matchesHandler;
- const matchesBoth = matchesType && matchesHandler;
- if ( log === 1 && matchesBoth || log === 2 && matchesEither || log === 3 ) {
- safe.uboLog(`addEventListener('${type}', ${handler})`);
+ if ( type === '' && pattern === '' ) {
+ safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`);
+ } else if ( shouldPrevent(thisArg, t, h) ) {
+ return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
}
- if ( debug === 1 && matchesBoth || debug === 2 && matchesEither ) {
- debugger; // jshint ignore:line
- }
- if ( matchesBoth ) { return; }
return Reflect.apply(target, thisArg, args);
},
get(target, prop, receiver) {
@@ -1533,19 +1721,28 @@ function jsonPrune(
stackNeedle = ''
) {
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('json-prune', rawPrunePaths, rawNeedlePaths, stackNeedle);
const stackNeedleDetails = safe.initPattern(stackNeedle, { canNegate: true });
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
JSON.parse = new Proxy(JSON.parse, {
apply: function(target, thisArg, args) {
const objBefore = Reflect.apply(target, thisArg, args);
+ if ( rawPrunePaths === '' ) {
+ safe.uboLog(logPrefix, safe.JSON_stringify(objBefore, null, 2));
+ }
const objAfter = objectPruneFn(
objBefore,
rawPrunePaths,
rawNeedlePaths,
stackNeedleDetails,
extraArgs
- );
- return objAfter || objBefore;
+ );
+ if ( objAfter === undefined ) { return objBefore; }
+ safe.uboLog(logPrefix, 'Pruned');
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `After pruning:\n${safe.JSON_stringify(objAfter, null, 2)}`);
+ }
+ return objAfter;
},
});
}
@@ -1579,7 +1776,6 @@ builtinScriptlets.push({
'object-prune.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
- 'should-log.fn',
],
});
function jsonPruneXhrResponse(
@@ -1587,10 +1783,9 @@ function jsonPruneXhrResponse(
rawNeedlePaths = ''
) {
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('json-prune-xhr-response', rawPrunePaths, rawNeedlePaths);
const xhrInstances = new WeakMap();
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
- const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log, });
- const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { });
const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url');
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
self.XMLHttpRequest = class extends self.XMLHttpRequest {
@@ -1602,10 +1797,10 @@ function jsonPruneXhrResponse(
outcome = 'nomatch';
}
}
- if ( outcome === logLevel || outcome === 'all' ) {
- log(`xhr.open(${method}, ${url}, ${args.join(', ')})`);
- }
if ( outcome === 'match' ) {
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Matched optional "propsToMatch", "${extraArgs.propsToMatch}"`);
+ }
xhrInstances.set(this, xhrDetails);
}
return super.open(method, url, ...args);
@@ -1630,8 +1825,10 @@ function jsonPruneXhrResponse(
if ( typeof innerResponse === 'object' ) {
objBefore = innerResponse;
} else if ( typeof innerResponse === 'string' ) {
- try { objBefore = safe.JSON_parse(innerResponse); }
- catch(ex) { }
+ try {
+ objBefore = safe.JSON_parse(innerResponse);
+ } catch(ex) {
+ }
}
if ( typeof objBefore !== 'object' ) {
return (xhrDetails.response = innerResponse);
@@ -1648,6 +1845,7 @@ function jsonPruneXhrResponse(
outerResponse = typeof innerResponse === 'string'
? safe.JSON_stringify(objAfter)
: objAfter;
+ safe.uboLog(logPrefix, 'Pruned');
} else {
outerResponse = innerResponse;
}
@@ -1826,9 +2024,9 @@ function noEvalIf(
/******************************************************************************/
builtinScriptlets.push({
- name: 'no-fetch-if.js',
+ name: 'prevent-fetch.js',
aliases: [
- 'prevent-fetch.js',
+ 'no-fetch-if.js',
],
fn: noFetchIf,
dependencies: [
@@ -1842,6 +2040,7 @@ function noFetchIf(
) {
if ( typeof propsToMatch !== 'string' ) { return; }
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody);
const needles = [];
for ( const condition of propsToMatch.split(/\s+/) ) {
if ( condition === '' ) { continue; }
@@ -1856,7 +2055,6 @@ function noFetchIf(
}
needles.push({ key, re: safe.patternToRegex(value) });
}
- const log = needles.length === 0 ? console.log.bind(console) : undefined;
self.fetch = new Proxy(self.fetch, {
apply: function(target, thisArg, args) {
const details = args[0] instanceof self.Request
@@ -1868,17 +2066,16 @@ function noFetchIf(
for ( const prop in details ) {
let v = details[prop];
if ( typeof v !== 'string' ) {
- try { v = JSON.stringify(v); }
+ try { v = safe.JSON_stringify(v); }
catch(ex) { }
}
if ( typeof v !== 'string' ) { continue; }
props.set(prop, v);
}
- if ( log !== undefined ) {
- const out = Array.from(props)
- .map(a => `${a[0]}:${a[1]}`)
- .join(' ');
- log(`uBO: fetch(${out})`);
+ if ( propsToMatch === '' && responseBody === '' ) {
+ const out = Array.from(props).map(a => `${a[0]}:${a[1]}`);
+ safe.uboLog(logPrefix, `Called: ${out.join('\n')}`);
+ return Reflect.apply(target, thisArg, args);
}
proceed = needles.length === 0;
for ( const { key, re } of needles ) {
@@ -1902,21 +2099,23 @@ function noFetchIf(
responseType = desURL.origin !== document.location.origin
? 'cors'
: 'basic';
- } catch(_) {
+ } catch(ex) {
+ safe.uboErr(logPrefix, `Error: ${ex}`);
}
}
return generateContentFn(responseBody).then(text => {
+ safe.uboLog(logPrefix, `Prevented with response "${text}"`);
const response = new Response(text, {
statusText: 'OK',
headers: {
'Content-Length': text.length,
}
});
- Object.defineProperty(response, 'url', {
+ safe.Object_defineProperty(response, 'url', {
value: details.url
});
if ( responseType !== '' ) {
- Object.defineProperty(response, 'type', {
+ safe.Object_defineProperty(response, 'type', {
value: responseType
});
}
@@ -1937,6 +2136,7 @@ builtinScriptlets.push({
world: 'ISOLATED',
dependencies: [
'run-at.fn',
+ 'safe-self.fn',
],
});
// https://www.reddit.com/r/uBlockOrigin/comments/q0frv0/while_reading_a_sports_article_i_was_redirected/hf7wo9v/
@@ -1944,9 +2144,12 @@ function preventRefresh(
arg1 = ''
) {
if ( typeof arg1 !== 'string' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('prevent-refresh', arg1);
const defuse = ( ) => {
const meta = document.querySelector('meta[http-equiv="refresh" i][content]');
if ( meta === null ) { return; }
+ safe.uboLog(logPrefix, `Prevented "${meta.textContent}"`);
const s = arg1 === ''
? meta.getAttribute('content')
: arg1;
@@ -2146,13 +2349,13 @@ builtinScriptlets.push({
],
fn: setConstant,
dependencies: [
- 'set-constant-core.fn'
+ 'set-constant.fn'
],
});
function setConstant(
...args
) {
- setConstantCore(false, ...args);
+ setConstantFn(false, ...args);
}
/******************************************************************************/
@@ -2175,6 +2378,7 @@ function noSetIntervalIf(
) {
if ( typeof needle !== 'string' ) { return; }
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('prevent-setInterval', needle, delay);
const needleNot = needle.charAt(0) === '!';
if ( needleNot ) { needle = needle.slice(1); }
if ( delay === '' ) { delay = undefined; }
@@ -2184,9 +2388,6 @@ function noSetIntervalIf(
if ( delayNot ) { delay = delay.slice(1); }
delay = parseInt(delay, 10);
}
- const log = needleNot === false && needle === '' && delay === undefined
- ? console.log
- : undefined;
const reNeedle = safe.patternToRegex(needle);
self.setInterval = new Proxy(self.setInterval, {
apply: function(target, thisArg, args) {
@@ -2194,19 +2395,20 @@ function noSetIntervalIf(
? String(safe.Function_toString(args[0]))
: String(args[0]);
const b = args[1];
- if ( log !== undefined ) {
- log('uBO: setInterval("%s", %s)', a, b);
- } else {
- let defuse;
- if ( needle !== '' ) {
- defuse = reNeedle.test(a) !== needleNot;
- }
- if ( defuse !== false && delay !== undefined ) {
- defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
- }
- if ( defuse ) {
- args[0] = function(){};
- }
+ if ( needle === '' && delay === undefined ) {
+ safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
+ return Reflect.apply(target, thisArg, args);
+ }
+ let defuse;
+ if ( needle !== '' ) {
+ defuse = reNeedle.test(a) !== needleNot;
+ }
+ if ( defuse !== false && delay !== undefined ) {
+ defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
+ }
+ if ( defuse ) {
+ args[0] = function(){};
+ safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
}
return Reflect.apply(target, thisArg, args);
},
@@ -2239,6 +2441,7 @@ function noSetTimeoutIf(
) {
if ( typeof needle !== 'string' ) { return; }
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('prevent-setTimeout', needle, delay);
const needleNot = needle.charAt(0) === '!';
if ( needleNot ) { needle = needle.slice(1); }
if ( delay === '' ) { delay = undefined; }
@@ -2248,9 +2451,6 @@ function noSetTimeoutIf(
if ( delayNot ) { delay = delay.slice(1); }
delay = parseInt(delay, 10);
}
- const log = needleNot === false && needle === '' && delay === undefined
- ? console.log
- : undefined;
const reNeedle = safe.patternToRegex(needle);
self.setTimeout = new Proxy(self.setTimeout, {
apply: function(target, thisArg, args) {
@@ -2258,19 +2458,20 @@ function noSetTimeoutIf(
? String(safe.Function_toString(args[0]))
: String(args[0]);
const b = args[1];
- if ( log !== undefined ) {
- log('uBO: setTimeout("%s", %s)', a, b);
- } else {
- let defuse;
- if ( needle !== '' ) {
- defuse = reNeedle.test(a) !== needleNot;
- }
- if ( defuse !== false && delay !== undefined ) {
- defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
- }
- if ( defuse ) {
- args[0] = function(){};
- }
+ if ( needle === '' && delay === undefined ) {
+ safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
+ return Reflect.apply(target, thisArg, args);
+ }
+ let defuse;
+ if ( needle !== '' ) {
+ defuse = reNeedle.test(a) !== needleNot;
+ }
+ if ( defuse !== false && delay !== undefined ) {
+ defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
+ }
+ if ( defuse ) {
+ args[0] = function(){};
+ safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
}
return Reflect.apply(target, thisArg, args);
},
@@ -2371,10 +2572,11 @@ function noXhrIf(
directive = ''
) {
if ( typeof propsToMatch !== 'string' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('prevent-xhr', propsToMatch, directive);
const xhrInstances = new WeakMap();
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
- const log = propNeedles.size === 0 ? console.log.bind(console) : undefined;
- const warOrigin = scriptletGlobals.get('warOrigin');
+ const warOrigin = scriptletGlobals.warOrigin;
const headers = {
'date': '',
'content-type': '',
@@ -2382,15 +2584,15 @@ function noXhrIf(
};
self.XMLHttpRequest = class extends self.XMLHttpRequest {
open(method, url, ...args) {
- if ( log !== undefined ) {
- log(`uBO: xhr.open(${method}, ${url}, ${args.join(', ')})`);
- return super.open(method, url, ...args);
- }
xhrInstances.delete(this);
if ( warOrigin !== undefined && url.startsWith(warOrigin) ) {
return super.open(method, url, ...args);
}
const haystack = { method, url };
+ if ( propsToMatch === '' && directive === '' ) {
+ safe.uboLog(logPrefix, `Called: ${safe.JSON_stringify(haystack, null, 2)}`);
+ return super.open(method, url, ...args);
+ }
if ( matchObjectProperties(propNeedles, haystack) ) {
xhrInstances.set(this, haystack);
}
@@ -2468,6 +2670,7 @@ function noXhrIf(
details.xhr.dispatchEvent(new Event('readystatechange'));
details.xhr.dispatchEvent(new Event('load'));
details.xhr.dispatchEvent(new Event('loadend'));
+ safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`);
});
}
getResponseHeader(headerName) {
@@ -2507,7 +2710,6 @@ builtinScriptlets.push({
fn: noWindowOpenIf,
dependencies: [
'safe-self.fn',
- 'should-log.fn',
],
});
function noWindowOpenIf(
@@ -2516,6 +2718,7 @@ function noWindowOpenIf(
decoy = ''
) {
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('no-window-open-if', pattern, delay, decoy);
const targetMatchResult = pattern.startsWith('!') === false;
if ( targetMatchResult === false ) {
pattern = pattern.slice(1);
@@ -2525,8 +2728,6 @@ function noWindowOpenIf(
if ( isNaN(autoRemoveAfter) ) {
autoRemoveAfter = -1;
}
- const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
- const logLevel = shouldLog(extraArgs);
const createDecoy = function(tag, urlProp, url) {
const decoyElem = document.createElement(tag);
decoyElem[urlProp] = url;
@@ -2541,12 +2742,13 @@ function noWindowOpenIf(
window.open = new Proxy(window.open, {
apply: function(target, thisArg, args) {
const haystack = args.join(' ');
- if ( logLevel ) {
- safe.uboLog('window.open:', haystack);
- }
if ( rePattern.test(haystack) !== targetMatchResult ) {
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Allowed (${args.join(', ')})`);
+ }
return Reflect.apply(target, thisArg, args);
}
+ safe.uboLog(logPrefix, `Prevented (${args.join(', ')})`);
if ( autoRemoveAfter < 0 ) { return null; }
const decoyElem = decoy === 'obj'
? createDecoy('object', 'data', ...args)
@@ -2568,14 +2770,14 @@ function noWindowOpenIf(
},
});
}
- if ( logLevel ) {
+ if ( safe.logLevel !== 0 ) {
popup = new Proxy(popup, {
get: function(target, prop) {
- safe.uboLog('window.open / get', prop, '===', target[prop]);
+ safe.uboLog(logPrefix, 'window.open / get', prop, '===', target[prop]);
return Reflect.get(...arguments);
},
set: function(target, prop, value) {
- safe.uboLog('window.open / set', prop, '=', value);
+ safe.uboLog(logPrefix, 'window.open / set', prop, '=', value);
return Reflect.set(...arguments);
},
});
@@ -2758,31 +2960,6 @@ function noWebrtc() {
/******************************************************************************/
builtinScriptlets.push({
- name: 'golem.de.js',
- fn: golemDe,
-});
-// https://github.com/uBlockOrigin/uAssets/issues/88
-function golemDe() {
- const rael = window.addEventListener;
- window.addEventListener = function(a, b) {
- rael(...arguments);
- let haystack;
- try {
- haystack = b.toString();
- } catch(ex) {
- }
- if (
- typeof haystack === 'string' &&
- /^\s*function\s*\(\)\s*\{\s*window\.clearTimeout\(r\)\s*\}\s*$/.test(haystack)
- ) {
- b();
- }
- }.bind(window);
-}
-
-/******************************************************************************/
-
-builtinScriptlets.push({
name: 'disable-newtab-links.js',
fn: disableNewtabLinks,
});
@@ -2884,7 +3061,6 @@ builtinScriptlets.push({
fn: xmlPrune,
dependencies: [
'safe-self.fn',
- 'should-log.fn',
],
});
function xmlPrune(
@@ -2895,9 +3071,9 @@ function xmlPrune(
if ( typeof selector !== 'string' ) { return; }
if ( selector === '' ) { return; }
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('xml-prune', selector, selectorCheck, urlPattern);
const reUrl = safe.patternToRegex(urlPattern);
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
- const log = shouldLog(extraArgs) ? ((...args) => { safe.uboLog(...args); }) : (( ) => { });
const queryAll = (xmlDoc, selector) => {
const isXpath = /^xpath\(.+\)$/.test(selector);
if ( isXpath === false ) {
@@ -2924,21 +3100,21 @@ function xmlPrune(
}
if ( extraArgs.logdoc ) {
const serializer = new XMLSerializer();
- log(`xmlPrune: document is\n\t${serializer.serializeToString(xmlDoc)}`);
+ safe.uboLog(logPrefix, `Document is\n\t${serializer.serializeToString(xmlDoc)}`);
}
const items = queryAll(xmlDoc, selector);
if ( items.length === 0 ) { return xmlDoc; }
- log(`xmlPrune: removing ${items.length} items`);
+ safe.uboLog(logPrefix, `Removing ${items.length} items`);
for ( const item of items ) {
if ( item.nodeType === 1 ) {
item.remove();
} else if ( item.nodeType === 2 ) {
item.ownerElement.removeAttribute(item.nodeName);
}
- log(`xmlPrune: ${item.constructor.name}.${item.nodeName} removed`);
+ safe.uboLog(logPrefix, `${item.constructor.name}.${item.nodeName} removed`);
}
} catch(ex) {
- log(ex);
+ safe.uboErr(logPrefix, `Error: ${ex}`);
}
return xmlDoc;
};
@@ -3030,7 +3206,6 @@ builtinScriptlets.push({
fn: m3uPrune,
dependencies: [
'safe-self.fn',
- 'should-log.fn',
],
});
// https://en.wikipedia.org/wiki/M3U
@@ -3040,9 +3215,8 @@ function m3uPrune(
) {
if ( typeof m3uPattern !== 'string' ) { return; }
const safe = safeSelf();
- const options = safe.getExtraArgs(Array.from(arguments), 2);
- const logLevel = shouldLog(options);
- const uboLog = logLevel ? ((...args) => safe.uboLog(...args)) : (( ) => { });
+ const logPrefix = safe.makeLogPrefix('m3u-prune', m3uPattern, urlPattern);
+ const toLog = [];
const regexFromArg = arg => {
if ( arg === '' ) { return /^/; }
const match = /^\/(.+)\/([gms]*)$/.exec(arg);
@@ -3061,22 +3235,22 @@ function m3uPrune(
if ( lines[i].startsWith('#EXT-X-CUE:TYPE="SpliceOut"') === false ) {
return false;
}
- uboLog('m3u-prune: discarding', `\n\t${lines[i]}`);
+ toLog.push(`\t${lines[i]}`);
lines[i] = undefined; i += 1;
if ( lines[i].startsWith('#EXT-X-ASSET:CAID') ) {
- uboLog(`\t${lines[i]}`);
+ toLog.push(`\t${lines[i]}`);
lines[i] = undefined; i += 1;
}
if ( lines[i].startsWith('#EXT-X-SCTE35:') ) {
- uboLog(`\t${lines[i]}`);
+ toLog.push(`\t${lines[i]}`);
lines[i] = undefined; i += 1;
}
if ( lines[i].startsWith('#EXT-X-CUE-IN') ) {
- uboLog(`\t${lines[i]}`);
+ toLog.push(`\t${lines[i]}`);
lines[i] = undefined; i += 1;
}
if ( lines[i].startsWith('#EXT-X-SCTE35:') ) {
- uboLog(`\t${lines[i]}`);
+ toLog.push(`\t${lines[i]}`);
lines[i] = undefined; i += 1;
}
return true;
@@ -3084,10 +3258,10 @@ function m3uPrune(
const pruneInfBlock = (lines, i) => {
if ( lines[i].startsWith('#EXTINF') === false ) { return false; }
if ( reM3u.test(lines[i+1]) === false ) { return false; }
- uboLog('m3u-prune: discarding', `\n\t${lines[i]}, \n\t${lines[i+1]}`);
+ toLog.push('Discarding', `\t${lines[i]}, \t${lines[i+1]}`);
lines[i] = lines[i+1] = undefined; i += 2;
if ( lines[i].startsWith('#EXT-X-DISCONTINUITY') ) {
- uboLog(`\t${lines[i]}`);
+ toLog.push(`\t${lines[i]}`);
lines[i] = undefined; i += 1;
}
return true;
@@ -3124,9 +3298,7 @@ function m3uPrune(
}
text = before.trim() + '\n' + after.trim();
reM3u.lastIndex = before.length + 1;
- uboLog('m3u-prune: discarding\n',
- discard.split(/\n+/).map(s => `\t${s}`).join('\n')
- );
+ toLog.push('Discarding', ...discard.split(/\n+/).map(s => `\t${s}`));
if ( reM3u.global === false ) { break; }
}
return text;
@@ -3151,13 +3323,18 @@ function m3uPrune(
return Reflect.apply(target, thisArg, args);
}
return realFetch(...args).then(realResponse =>
- realResponse.text().then(text =>
- new Response(pruner(text), {
+ realResponse.text().then(text => {
+ const response = new Response(pruner(text), {
status: realResponse.status,
statusText: realResponse.statusText,
headers: realResponse.headers,
- })
- )
+ });
+ if ( toLog.length !== 0 ) {
+ toLog.unshift(logPrefix);
+ safe.uboLog(toLog.join('\n'));
+ }
+ return response;
+ })
);
}
});
@@ -3175,6 +3352,10 @@ function m3uPrune(
if ( textout === textin ) { return; }
Object.defineProperty(thisArg, 'response', { value: textout });
Object.defineProperty(thisArg, 'responseText', { value: textout });
+ if ( toLog.length !== 0 ) {
+ toLog.unshift(logPrefix);
+ safe.uboLog(toLog.join('\n'));
+ }
});
return Reflect.apply(target, thisArg, args);
}
@@ -3220,6 +3401,7 @@ builtinScriptlets.push({
world: 'ISOLATED',
dependencies: [
'run-at.fn',
+ 'safe-self.fn',
],
});
function hrefSanitizer(
@@ -3228,6 +3410,8 @@ function hrefSanitizer(
) {
if ( typeof selector !== 'string' ) { return; }
if ( selector === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('href-sanitizer', selector, source);
if ( source === '' ) { source = 'text'; }
const sanitizeCopycats = (href, text) => {
let elems = [];
@@ -3239,6 +3423,7 @@ function hrefSanitizer(
for ( const elem of elems ) {
elem.setAttribute('href', text);
}
+ return elems.length;
};
const validateURL = text => {
if ( text === '' ) { return ''; }
@@ -3266,7 +3451,7 @@ function hrefSanitizer(
return elem.textContent
.replace(/^[^\x21-\x7e]+/, '') // remove leading invalid characters
.replace(/[^\x21-\x7e]+$/, '') // remove trailing invalid characters
- ;
+ ;
}
return '';
};
@@ -3287,7 +3472,8 @@ function hrefSanitizer(
if ( hrefAfter === '' ) { continue; }
if ( hrefAfter === href ) { continue; }
elem.setAttribute('href', hrefAfter);
- sanitizeCopycats(href, hrefAfter);
+ const count = sanitizeCopycats(href, hrefAfter);
+ safe.uboLog(logPrefix, `Sanitized ${count+1} links to\n${hrefAfter}`);
}
return true;
};
@@ -3305,7 +3491,7 @@ function hrefSanitizer(
if ( shouldSanitize ) { break; }
}
if ( shouldSanitize === false ) { return; }
- timer = self.requestAnimationFrame(( ) => {
+ timer = self.requestIdleCallback(( ) => {
timer = undefined;
sanitize();
});
@@ -3402,21 +3588,28 @@ function spoofCSS(
propToValueMap.set(toCamelCase(args[i+0]), args[i+1]);
}
const safe = safeSelf();
- const canDebug = scriptletGlobals.has('canDebug');
+ const logPrefix = safe.makeLogPrefix('spoof-css', selector, ...args);
+ const canDebug = scriptletGlobals.canDebug;
const shouldDebug = canDebug && propToValueMap.get('debug') || 0;
- const shouldLog = canDebug && propToValueMap.has('log') || 0;
+ const instanceProperties = [ 'cssText', 'length', 'parentRule' ];
const spoofStyle = (prop, real) => {
const normalProp = toCamelCase(prop);
const shouldSpoof = propToValueMap.has(normalProp);
const value = shouldSpoof ? propToValueMap.get(normalProp) : real;
- if ( shouldLog === 2 || shouldSpoof && shouldLog === 1 ) {
- safe.uboLog(prop, value);
+ if ( shouldSpoof ) {
+ safe.uboLog(logPrefix, `Spoofing ${prop} to ${value}`);
}
return value;
};
+ const cloackFunc = (fn, thisArg, name) => {
+ const trap = fn.bind(thisArg);
+ Object.defineProperty(trap, 'name', { value: name });
+ return trap;
+ };
self.getComputedStyle = new Proxy(self.getComputedStyle, {
apply: function(target, thisArg, args) {
- if ( shouldDebug !== 0 ) { debugger; } // jshint ignore: line
+ // eslint-disable-next-line no-debugger
+ if ( shouldDebug !== 0 ) { debugger; }
const style = Reflect.apply(target, thisArg, args);
const targetElements = new WeakSet(document.querySelectorAll(selector));
if ( targetElements.has(args[0]) === false ) { return style; }
@@ -3424,11 +3617,14 @@ function spoofCSS(
get(target, prop, receiver) {
if ( typeof target[prop] === 'function' ) {
if ( prop === 'getPropertyValue' ) {
- return (function(prop) {
+ return cloackFunc(function(prop) {
return spoofStyle(prop, target[prop]);
- }).bind(target);
+ }, target, 'getPropertyValue');
}
- return target[prop].bind(target);
+ return cloackFunc(target[prop], target, prop);
+ }
+ if ( instanceProperties.includes(prop) ) {
+ return Reflect.get(target, prop);
}
return spoofStyle(prop, Reflect.get(target, prop, receiver));
},
@@ -3455,7 +3651,8 @@ function spoofCSS(
});
Element.prototype.getBoundingClientRect = new Proxy(Element.prototype.getBoundingClientRect, {
apply: function(target, thisArg, args) {
- if ( shouldDebug !== 0 ) { debugger; } // jshint ignore: line
+ // eslint-disable-next-line no-debugger
+ if ( shouldDebug !== 0 ) { debugger; }
const rect = Reflect.apply(target, thisArg, args);
const targetElements = new WeakSet(document.querySelectorAll(selector));
if ( targetElements.has(thisArg) === false ) { return rect; }
@@ -3524,7 +3721,8 @@ function setCookie(
path = ''
) {
if ( name === '' ) { return; }
- name = encodeURIComponent(name);
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
const validValues = [
'accept', 'reject',
@@ -3545,17 +3743,21 @@ function setCookie(
if ( validValues.includes(unquoted) === false ) {
if ( /^\d+$/.test(unquoted) === false ) { return; }
const n = parseInt(value, 10);
- if ( n > 15 ) { return; }
+ if ( n > 32767 ) { return; }
}
- setCookieFn(
+ const done = setCookieFn(
false,
name,
value,
'',
path,
- safeSelf().getExtraArgs(Array.from(arguments), 3)
+ safe.getExtraArgs(Array.from(arguments), 3)
);
+
+ if ( done ) {
+ safe.uboLog(logPrefix, 'Done');
+ }
}
// For compatibility with AdGuard
@@ -3641,6 +3843,7 @@ builtinScriptlets.push({
world: 'ISOLATED',
dependencies: [
'run-at.fn',
+ 'safe-self.fn',
],
});
function setAttr(
@@ -3648,9 +3851,11 @@ function setAttr(
attr = '',
value = ''
) {
- if ( typeof selector !== 'string' ) { return; }
if ( selector === '' ) { return; }
+ if ( attr === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('set-attr', attr, value);
const validValues = [ '', 'false', 'true' ];
let copyFrom = '';
@@ -3685,7 +3890,11 @@ function setAttr(
const before = elem.getAttribute(attr);
const after = extractValue(elem);
if ( after === before ) { continue; }
+ if ( after !== '' && /^on/i.test(attr) ) {
+ if ( attr.toLowerCase() in elem ) { continue; }
+ }
elem.setAttribute(attr, after);
+ safe.uboLog(logPrefix, `${attr}="${after}"`);
}
return true;
};
@@ -3806,6 +4015,58 @@ function multiup() {
document.addEventListener('click', handler, { capture: true });
}
+/******************************************************************************/
+
+builtinScriptlets.push({
+ name: 'remove-cache-storage-item.js',
+ fn: removeCacheStorageItem,
+ world: 'ISOLATED',
+ dependencies: [
+ 'safe-self.fn',
+ ],
+});
+function removeCacheStorageItem(
+ cacheNamePattern = '',
+ requestPattern = ''
+) {
+ if ( cacheNamePattern === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('remove-cache-storage-item', cacheNamePattern, requestPattern);
+ const cacheStorage = self.caches;
+ if ( cacheStorage instanceof Object === false ) { return; }
+ const reCache = safe.patternToRegex(cacheNamePattern, undefined, true);
+ const reRequest = safe.patternToRegex(requestPattern, undefined, true);
+ cacheStorage.keys().then(cacheNames => {
+ for ( const cacheName of cacheNames ) {
+ if ( reCache.test(cacheName) === false ) { continue; }
+ if ( requestPattern === '' ) {
+ cacheStorage.delete(cacheName).then(result => {
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Deleting ${cacheName}`);
+ }
+ if ( result !== true ) { return; }
+ safe.uboLog(logPrefix, `Deleted ${cacheName}: ${result}`);
+ });
+ continue;
+ }
+ cacheStorage.open(cacheName).then(cache => {
+ cache.keys().then(requests => {
+ for ( const request of requests ) {
+ if ( reRequest.test(request.url) === false ) { continue; }
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Deleting ${cacheName}/${request.url}`);
+ }
+ cache.delete(request).then(result => {
+ if ( result !== true ) { return; }
+ safe.uboLog(logPrefix, `Deleted ${cacheName}/${request.url}: ${result}`);
+ });
+ }
+ });
+ });
+ }
+ });
+}
+
/*******************************************************************************
*
@@ -3883,13 +4144,13 @@ builtinScriptlets.push({
],
fn: trustedSetConstant,
dependencies: [
- 'set-constant-core.fn'
+ 'set-constant.fn'
],
});
function trustedSetConstant(
...args
) {
- setConstantCore(true, ...args);
+ setConstantFn(true, ...args);
}
/*******************************************************************************
@@ -3921,6 +4182,8 @@ function trustedSetCookie(
) {
if ( name === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
const time = new Date();
if ( value === '$now$' ) {
@@ -3942,7 +4205,7 @@ function trustedSetCookie(
expires = time.toUTCString();
}
- setCookieFn(
+ const done = setCookieFn(
true,
name,
value,
@@ -3950,6 +4213,10 @@ function trustedSetCookie(
path,
safeSelf().getExtraArgs(Array.from(arguments), 4)
);
+
+ if ( done ) {
+ safe.uboLog(logPrefix, 'Done');
+ }
}
// For compatibility with AdGuard
@@ -4018,6 +4285,9 @@ function trustedSetSessionStorageItem(key = '', value = '') {
builtinScriptlets.push({
name: 'trusted-replace-fetch-response.js',
requiresTrust: true,
+ aliases: [
+ 'trusted-rpfr.js',
+ ],
fn: trustedReplaceFetchResponse,
dependencies: [
'replace-fetch-response.fn',
@@ -4037,7 +4307,6 @@ builtinScriptlets.push({
'match-object-properties.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
- 'should-log.fn',
],
});
function trustedReplaceXhrResponse(
@@ -4046,12 +4315,8 @@ function trustedReplaceXhrResponse(
propsToMatch = ''
) {
const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('trusted-replace-xhr-response', pattern, replacement, propsToMatch);
const xhrInstances = new WeakMap();
- const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
- const logLevel = shouldLog({
- log: pattern === '' && 'all' || extraArgs.log,
- });
- const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { });
if ( pattern === '*' ) { pattern = '.*'; }
const rePattern = safe.patternToRegex(pattern);
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
@@ -4065,10 +4330,10 @@ function trustedReplaceXhrResponse(
outcome = 'nomatch';
}
}
- if ( outcome === logLevel || outcome === 'all' ) {
- log(`xhr.open(${method}, ${url}, ${args.join(', ')})`);
- }
if ( outcome === 'match' ) {
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Matched "propsToMatch"`);
+ }
xhrInstances.set(outerXhr, xhrDetails);
}
return super.open(method, url, ...args);
@@ -4094,13 +4359,8 @@ function trustedReplaceXhrResponse(
}
const textBefore = innerResponse;
const textAfter = textBefore.replace(rePattern, replacement);
- const outcome = textAfter !== textBefore ? 'match' : 'nomatch';
- if ( outcome === logLevel || logLevel === 'all' ) {
- log(
- `trusted-replace-xhr-response (${outcome})`,
- `\n\tpattern: ${pattern}`,
- `\n\treplacement: ${replacement}`,
- );
+ if ( textAfter !== textBefore ) {
+ safe.uboLog(logPrefix, 'Match');
}
return (xhrDetails.response = textAfter);
}
@@ -4129,22 +4389,63 @@ builtinScriptlets.push({
fn: trustedClickElement,
world: 'ISOLATED',
dependencies: [
+ 'get-all-cookies.fn',
+ 'get-all-local-storage.fn',
'run-at-html-element.fn',
'safe-self.fn',
],
});
function trustedClickElement(
selectors = '',
- extraMatch = '', // not yet supported
+ extraMatch = '',
delay = ''
) {
- if ( extraMatch !== '' ) { return; }
-
const safe = safeSelf();
- const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
- const uboLog = extraArgs.log !== undefined
- ? ((...args) => { safe.uboLog(...args); })
- : (( ) => { });
+ const logPrefix = safe.makeLogPrefix('trusted-click-element', selectors, extraMatch, delay);
+
+ if ( extraMatch !== '' ) {
+ const assertions = extraMatch.split(',').map(s => {
+ const pos1 = s.indexOf(':');
+ const s1 = pos1 !== -1 ? s.slice(0, pos1) : s;
+ const not = s1.startsWith('!');
+ const type = not ? s1.slice(1) : s1;
+ const s2 = pos1 !== -1 ? s.slice(pos1+1).trim() : '';
+ if ( s2 === '' ) { return; }
+ const out = { not, type };
+ const match = /^\/(.+)\/(i?)$/.exec(s2);
+ if ( match !== null ) {
+ out.re = new RegExp(match[1], match[2] || undefined);
+ return out;
+ }
+ const pos2 = s2.indexOf('=');
+ const key = pos2 !== -1 ? s2.slice(0, pos2).trim() : s2;
+ const value = pos2 !== -1 ? s2.slice(pos2+1).trim() : '';
+ out.re = new RegExp(`^${this.escapeRegexChars(key)}=${this.escapeRegexChars(value)}`);
+ return out;
+ }).filter(details => details !== undefined);
+ const allCookies = assertions.some(o => o.type === 'cookie')
+ ? getAllCookiesFn()
+ : [];
+ const allStorageItems = assertions.some(o => o.type === 'localStorage')
+ ? getAllLocalStorageFn()
+ : [];
+ const hasNeedle = (haystack, needle) => {
+ for ( const { key, value } of haystack ) {
+ if ( needle.test(`${key}=${value}`) ) { return true; }
+ }
+ return false;
+ };
+ for ( const { not, type, re } of assertions ) {
+ switch ( type ) {
+ case 'cookie':
+ if ( hasNeedle(allCookies, re) === not ) { return; }
+ break;
+ case 'localStorage':
+ if ( hasNeedle(allStorageItems, re) === not ) { return; }
+ break;
+ }
+ }
+ }
const querySelectorEx = (selector, context = document) => {
const pos = selector.indexOf(' >>> ');
@@ -4181,12 +4482,12 @@ function trustedClickElement(
const next = notFound => {
if ( selectorList.length === 0 ) {
- uboLog(`trusted-click-element: Completed`);
+ safe.uboLog(logPrefix, 'Completed');
return terminate();
}
const tnow = Date.now();
if ( tnow >= tbye ) {
- uboLog(`trusted-click-element: Timed out`);
+ safe.uboLog(logPrefix, 'Timed out');
return terminate();
}
if ( notFound ) { observe(); }
@@ -4195,7 +4496,7 @@ function trustedClickElement(
next.timer = undefined;
process();
}, delay);
- uboLog(`trusted-click-element: Waiting for ${selectorList[0]}...`);
+ safe.uboLog(logPrefix, `Waiting for ${selectorList[0]}...`);
};
next.stop = ( ) => {
if ( next.timer === undefined ) { return; }
@@ -4239,7 +4540,7 @@ function trustedClickElement(
selectorList.unshift(selector);
return next(true);
}
- uboLog(`trusted-click-element: Clicked ${selector}`);
+ safe.uboLog(logPrefix, `Clicked ${selector}`);
elem.click();
tnext += clickDelay;
next();
@@ -4336,40 +4637,67 @@ builtinScriptlets.push({
fn: trustedPruneOutboundObject,
dependencies: [
'object-prune.fn',
+ 'proxy-apply.fn',
'safe-self.fn',
],
});
function trustedPruneOutboundObject(
- entryPoint = '',
+ propChain = '',
rawPrunePaths = '',
rawNeedlePaths = ''
) {
- if ( entryPoint === '' ) { return; }
- let context = globalThis;
- let prop = entryPoint;
- for (;;) {
- const pos = prop.indexOf('.');
- if ( pos === -1 ) { break; }
- context = context[prop.slice(0, pos)];
- if ( context instanceof Object === false ) { return; }
- prop = prop.slice(pos+1);
- }
- if ( typeof context[prop] !== 'function' ) { return; }
+ if ( propChain === '' ) { return; }
const safe = safeSelf();
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
- context[prop] = new Proxy(context[prop], {
- apply: function(target, thisArg, args) {
- const objBefore = Reflect.apply(target, thisArg, args);
- if ( objBefore instanceof Object === false ) { return objBefore; }
- const objAfter = objectPruneFn(
- objBefore,
- rawPrunePaths,
- rawNeedlePaths,
- { matchAll: true },
- extraArgs
- );
- return objAfter || objBefore;
- },
+ const reflector = proxyApplyFn(propChain, function(...args) {
+ const objBefore = reflector(...args);
+ if ( objBefore instanceof Object === false ) { return objBefore; }
+ const objAfter = objectPruneFn(
+ objBefore,
+ rawPrunePaths,
+ rawNeedlePaths,
+ { matchAll: true },
+ extraArgs
+ );
+ return objAfter || objBefore;
+ });
+}
+
+/******************************************************************************/
+
+builtinScriptlets.push({
+ name: 'trusted-replace-argument.js',
+ requiresTrust: true,
+ fn: trustedReplaceArgument,
+ dependencies: [
+ 'proxy-apply.fn',
+ 'safe-self.fn',
+ 'validate-constant.fn',
+ ],
+});
+function trustedReplaceArgument(
+ propChain = '',
+ argpos = '',
+ argraw = ''
+) {
+ if ( propChain === '' ) { return; }
+ if ( argpos === '' ) { return; }
+ if ( argraw === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('trusted-replace-argument', propChain, argpos, argraw);
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
+ const normalValue = validateConstantFn(true, argraw);
+ const reCondition = extraArgs.condition
+ ? safe.patternToRegex(extraArgs.condition)
+ : /^/;
+ const reflector = proxyApplyFn(propChain, function(...args) {
+ const arglist = args[args.length-1];
+ if ( Array.isArray(arglist) === false ) { return reflector(...args); }
+ const argBefore = arglist[argpos];
+ if ( reCondition.test(argBefore) === false ) { return reflector(...args); }
+ arglist[argpos] = normalValue;
+ safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${normalValue}`);
+ return reflector(...args);
});
}